mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-07-15 21:03:47 +00:00
Compare commits
140 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 937e04fa5c | |||
| a7fa40393d | |||
| b91627d91b | |||
| 451c96868a | |||
| d508ea2afc | |||
| 457b768c14 | |||
| a2b9b1d59f | |||
| 6d20e3fcac | |||
| cc0d6e0eb2 | |||
| 5c9e1c0d75 | |||
| 8ee63dbe9e | |||
| cc3384f8e2 | |||
| d92867223c | |||
| 2ac2787ec5 | |||
| f11c0906e1 | |||
| 42e7ab8141 | |||
| ced52ddd81 | |||
| b3a986d826 | |||
| 7be6f4203e | |||
| 6a04a5d430 | |||
| ee09a6808a | |||
| 0f75cd4d16 | |||
| ab69661751 | |||
| a31e855c36 | |||
| d4dc310c93 | |||
| 046a3d6855 | |||
| 5cd9de9ac5 | |||
| d85fb26a5d | |||
| 7756dc6a4c | |||
| ff76137339 | |||
| 61db35daad | |||
| 26c1cacc9d | |||
| 8dddbaa86d | |||
| d46fafb421 | |||
| 25178928a0 | |||
| 2fe9c0f854 | |||
| 2f68dc018e | |||
| 8b8e77214c | |||
| 89589dcf9f | |||
| 250ad975d4 | |||
| b649e73c80 | |||
| 839e36adb9 | |||
| f02c0c3163 | |||
| 83c41751ea | |||
| 8058ffd217 | |||
| a3db62b0f8 | |||
| dc43bf1329 | |||
| 0a7b1b2bd0 | |||
| 94e6617638 | |||
| 76995dbc1b | |||
| be6cda7d8a | |||
| 9130eb68a8 | |||
| 200428498f | |||
| f60c9c72aa | |||
| 487919cedc | |||
| b046aedcee | |||
| 0c5216933a | |||
| 191dc8ba24 | |||
| d88c890ba7 | |||
| a34c60947d | |||
| 56fddb6fcb | |||
| b9bd00f86e | |||
| d1ae7f659d | |||
| b127c5ff4f | |||
| 75a87400eb | |||
| d4dccdb574 | |||
| a34421f5b2 | |||
| 7a5cf94d45 | |||
| 134f58b38b | |||
| 84dcda9363 | |||
| d81cf20f5e | |||
| 86fd8574f1 | |||
| 6e175e2b36 | |||
| c7fba8dbb1 | |||
| 0c2ac176a7 | |||
| 59a38dad0a | |||
| 6ac536ebad | |||
| 12418eb5b2 | |||
| e364e9b38e | |||
| 402f9fa909 | |||
| 5ac25e2a26 | |||
| 5fbac89e67 | |||
| f2fb938e5f | |||
| cef420d1d8 | |||
| 94f69cee3f | |||
| 23e72aedfd | |||
| 7957ed06e4 | |||
| 5f1a2d9a17 | |||
| dbe12ce681 | |||
| d66efdb74f | |||
| c3d1855c65 | |||
| 4f4e5296a4 | |||
| 61da21c507 | |||
| 347e8c40df | |||
| f0ae53c41b | |||
| 53f9e888df | |||
| c1d06028c3 | |||
| fa64e98ef9 | |||
| 2df2c4e73d | |||
| 7d904c4a2c | |||
| cf945e3665 | |||
| 880907cc93 | |||
| 22162e5b9b | |||
| 981705e81d | |||
| 5445cb2b78 | |||
| 2667af9437 | |||
| 58947f4455 | |||
| b7f52d4d73 | |||
| b854a4956c | |||
| 07367b24b6 | |||
| 1b076cc612 | |||
| 81d1db09e4 | |||
| f62bb7f773 | |||
| 0906ddfe6e | |||
| 96864dbeb4 | |||
| 3a663f1b47 | |||
| b9d5965aa6 | |||
| 47f8d3f23b | |||
| c71b8b4211 | |||
| 07a8fc3e50 | |||
| c90c45ec61 | |||
| 4e93ac7d88 | |||
| 9ce69a80d7 | |||
| 602f21b475 | |||
| 3b347f7ae5 | |||
| 1f83fb68f0 | |||
| a2481362a2 | |||
| f49c32d26f | |||
| 18e3f7c6fc | |||
| 6e5b34f878 | |||
| b0880129ad | |||
| b27916852f | |||
| 7b577cb343 | |||
| fc7731041e | |||
| 7dc1de913f | |||
| 30b07ba8f2 | |||
| c69e03d783 | |||
| 2ff31f5a82 | |||
| 08f9143993 | |||
| d0b139e388 |
+129
@@ -0,0 +1,129 @@
|
||||
# Dependencies
|
||||
node_modules
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Build outputs
|
||||
dist
|
||||
build
|
||||
.next
|
||||
.nuxt
|
||||
|
||||
# Development files
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
# IDE and editor files
|
||||
.vscode
|
||||
.idea
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# OS generated files
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
|
||||
# Git
|
||||
.git
|
||||
.gitignore
|
||||
|
||||
# Documentation
|
||||
README.md
|
||||
README-CN.md
|
||||
CONTRIBUTING.md
|
||||
LICENSE
|
||||
|
||||
# Docker files (avoid copying docker files into docker)
|
||||
# docker/ - commented out to allow entrypoint.sh to be copied
|
||||
|
||||
# Repository images
|
||||
repo-images/
|
||||
|
||||
# Uploads directory
|
||||
uploads/
|
||||
|
||||
# Electron files (not needed for Docker)
|
||||
electron/
|
||||
electron-builder.json
|
||||
|
||||
# Development and build artifacts
|
||||
*.log
|
||||
*.tmp
|
||||
*.temp
|
||||
|
||||
# Font files (we'll optimize these in Dockerfile)
|
||||
# public/fonts/*.ttf
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Dependency directories
|
||||
jspm_packages/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# next.js build output
|
||||
.next
|
||||
|
||||
# nuxt.js build output
|
||||
.nuxt
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
@@ -0,0 +1 @@
|
||||
github: [LukeGus]
|
||||
@@ -0,0 +1,82 @@
|
||||
name: Bug report
|
||||
description: Create a report to help Termix improve
|
||||
title: "[BUG]"
|
||||
labels: [bug]
|
||||
assignees: []
|
||||
body:
|
||||
- type: input
|
||||
id: title
|
||||
attributes:
|
||||
label: Title
|
||||
description: Brief, descriptive title for the bug
|
||||
placeholder: "Brief description of the bug"
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: platform
|
||||
attributes:
|
||||
label: Platform
|
||||
description: How are you using Termix?
|
||||
options:
|
||||
- Website - Firefox
|
||||
- Website - Safari
|
||||
- Website - Chrome
|
||||
- Website - Other Browser
|
||||
- App - Windows
|
||||
- App - Linux
|
||||
- App - iOS
|
||||
- App - Android
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: server-installation-method
|
||||
attributes:
|
||||
label: Server Installation Method
|
||||
description: How is the Termix server installed?
|
||||
options:
|
||||
- Docker
|
||||
- Manual Build
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: Version
|
||||
description: Find your version in the User Profile tab
|
||||
placeholder: "e.g., 1.7.0"
|
||||
validations:
|
||||
required: true
|
||||
- type: checkboxes
|
||||
id: troubleshooting
|
||||
attributes:
|
||||
label: Troubleshooting
|
||||
description: Please check all that apply
|
||||
options:
|
||||
- label: I have examined logs and tried to find the issue
|
||||
- label: I have reviewed opened and closed issues
|
||||
- label: I have tried restarting the application
|
||||
- type: textarea
|
||||
id: problem-description
|
||||
attributes:
|
||||
label: The Problem
|
||||
description: Describe the bug in detail. Include as much information as possible with screenshots if applicable.
|
||||
placeholder: "Describe what went wrong..."
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: reproduction-steps
|
||||
attributes:
|
||||
label: How to Reproduce
|
||||
description: Use as few steps as possible to reproduce the issue
|
||||
placeholder: |
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: additional-context
|
||||
attributes:
|
||||
label: Additional Context
|
||||
description: Any other context about the problem
|
||||
placeholder: "Add any other context about the problem here..."
|
||||
@@ -0,0 +1,36 @@
|
||||
name: Feature request
|
||||
description: Suggest an idea for Termix
|
||||
title: "[FEATURE]"
|
||||
labels: [enhancement]
|
||||
assignees: []
|
||||
body:
|
||||
- type: input
|
||||
id: title
|
||||
attributes:
|
||||
label: Title
|
||||
description: Brief, descriptive title for the feature request
|
||||
placeholder: "Brief description of the feature"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: related-issue
|
||||
attributes:
|
||||
label: Is it related to an issue?
|
||||
description: Describe the problem this feature would solve
|
||||
placeholder: "Describe what problem this feature would solve..."
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: solution
|
||||
attributes:
|
||||
label: The Solution
|
||||
description: Describe your proposed solution in detail
|
||||
placeholder: "Describe how you envision this feature working..."
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: additional-context
|
||||
attributes:
|
||||
label: Additional Context
|
||||
description: Any other context or screenshots about the feature request
|
||||
placeholder: "Add any other context about the feature request here..."
|
||||
@@ -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"
|
||||
@@ -0,0 +1,31 @@
|
||||
# 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/LukeGus/Termix/blob/main/CONTRIBUTING.md)
|
||||
@@ -1,25 +1,27 @@
|
||||
name: Build and Push Docker Image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- development
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- '.gitignore'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag_name:
|
||||
description: "Custom tag name for the Docker image"
|
||||
required: false
|
||||
default: ""
|
||||
registry:
|
||||
description: "Docker registry to push to"
|
||||
required: true
|
||||
default: "ghcr"
|
||||
type: choice
|
||||
options:
|
||||
- "ghcr"
|
||||
- "dockerhub"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
@@ -37,7 +39,7 @@ jobs:
|
||||
network=host
|
||||
|
||||
- name: Cache npm dependencies
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.npm
|
||||
@@ -48,38 +50,62 @@ jobs:
|
||||
${{ runner.os }}-node-
|
||||
|
||||
- name: Cache Docker layers
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: /tmp/.buildx-cache
|
||||
key: ${{ runner.os }}-buildx-${{ github.sha }}
|
||||
key: ${{ runner.os }}-buildx-${{ github.ref_name }}-${{ hashFiles('docker/Dockerfile') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-buildx-${{ github.ref_name }}-
|
||||
${{ runner.os }}-buildx-
|
||||
|
||||
- name: Login to Docker Registry
|
||||
- name: Login to GitHub Container Registry
|
||||
if: github.event.inputs.registry != 'dockerhub'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Login to Docker Hub
|
||||
if: github.event.inputs.registry == 'dockerhub'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: bugattiguy527
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Determine Docker image tag
|
||||
run: |
|
||||
echo "REPO_OWNER=$(echo ${{ github.repository_owner }} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV
|
||||
if [ "${{ github.event.inputs.tag_name }}" == "" ]; then
|
||||
IMAGE_TAG="${{ github.ref_name }}-development-latest"
|
||||
else
|
||||
REPO_OWNER=$(echo ${{ github.repository_owner }} | tr '[:upper:]' '[:lower:]')
|
||||
echo "REPO_OWNER=$REPO_OWNER" >> $GITHUB_ENV
|
||||
|
||||
if [ "${{ github.event.inputs.tag_name }}" != "" ]; then
|
||||
IMAGE_TAG="${{ github.event.inputs.tag_name }}"
|
||||
elif [ "${{ github.ref }}" == "refs/heads/main" ]; then
|
||||
IMAGE_TAG="latest"
|
||||
elif [ "${{ github.ref }}" == "refs/heads/development" ]; then
|
||||
IMAGE_TAG="development-latest"
|
||||
else
|
||||
IMAGE_TAG="${{ github.ref_name }}"
|
||||
fi
|
||||
echo "IMAGE_TAG=$IMAGE_TAG" >> $GITHUB_ENV
|
||||
|
||||
# Determine registry and image name
|
||||
if [ "${{ github.event.inputs.registry }}" == "dockerhub" ]; then
|
||||
echo "REGISTRY=docker.io" >> $GITHUB_ENV
|
||||
echo "IMAGE_NAME=bugattiguy527/termix" >> $GITHUB_ENV
|
||||
else
|
||||
echo "REGISTRY=ghcr.io" >> $GITHUB_ENV
|
||||
echo "IMAGE_NAME=$REPO_OWNER/termix" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: Build and Push Multi-Arch Docker Image
|
||||
uses: docker/build-push-action@v5
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: ./docker/Dockerfile
|
||||
push: true
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: ghcr.io/${{ env.REPO_OWNER }}/termix:${{ env.IMAGE_TAG }}
|
||||
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }}
|
||||
labels: |
|
||||
org.opencontainers.image.source=https://github.com/${{ github.repository }}
|
||||
org.opencontainers.image.revision=${{ github.sha }}
|
||||
@@ -95,14 +121,8 @@ jobs:
|
||||
rm -rf /tmp/.buildx-cache
|
||||
mv /tmp/.buildx-cache-new /tmp/.buildx-cache
|
||||
|
||||
- name: Notify via ntfy
|
||||
if: success()
|
||||
run: |
|
||||
curl -d "Docker image build and push completed successfully for tag: ${{ env.IMAGE_TAG }}" \
|
||||
https://ntfy.karmaa.site/termix-build
|
||||
|
||||
- name: Delete all untagged image versions
|
||||
if: success()
|
||||
if: success() && github.event.inputs.registry != 'dockerhub'
|
||||
uses: quartx-analytics/ghcr-cleaner@v1
|
||||
with:
|
||||
owner-type: user
|
||||
@@ -114,4 +134,4 @@ jobs:
|
||||
if: always()
|
||||
run: |
|
||||
docker image prune -af
|
||||
docker system prune -af --volumes
|
||||
docker system prune -af --volumes
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
name: Build Electron App
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
build_type:
|
||||
description: "Build type to run"
|
||||
required: true
|
||||
default: "all"
|
||||
type: choice
|
||||
options:
|
||||
- all
|
||||
- windows
|
||||
- linux
|
||||
|
||||
jobs:
|
||||
build-windows:
|
||||
runs-on: windows-latest
|
||||
if: github.event.inputs.build_type == 'all' || github.event.inputs.build_type == 'windows' || github.event.inputs.build_type == ''
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
cache: "npm"
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build Windows Portable
|
||||
run: npm run build:win-portable
|
||||
|
||||
- name: Build Windows Installer
|
||||
run: npm run build:win-installer
|
||||
|
||||
- name: Create Windows Portable zip
|
||||
run: |
|
||||
Compress-Archive -Path "release/win-unpacked/*" -DestinationPath "Termix-Windows-Portable.zip"
|
||||
|
||||
- name: Upload Windows Portable Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Termix-Windows-Portable
|
||||
path: Termix-Windows-Portable.zip
|
||||
retention-days: 30
|
||||
|
||||
- name: Upload Windows Installer Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Termix-Windows-Installer
|
||||
path: release/*.exe
|
||||
retention-days: 30
|
||||
|
||||
build-linux:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.inputs.build_type == 'all' || github.event.inputs.build_type == 'linux' || github.event.inputs.build_type == ''
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
cache: "npm"
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build Linux Portable
|
||||
run: npm run build:linux-portable
|
||||
|
||||
- name: Create Linux Portable zip
|
||||
run: |
|
||||
cd release/linux-unpacked
|
||||
zip -r ../../Termix-Linux-Portable.zip *
|
||||
cd ../..
|
||||
|
||||
- name: Upload Linux Portable Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Termix-Linux-Portable
|
||||
path: Termix-Linux-Portable.zip
|
||||
retention-days: 30
|
||||
+4
-139
@@ -22,143 +22,8 @@ dist-ssr
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# TypeScript v1 declaration files
|
||||
typings/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
/db/
|
||||
/release/
|
||||
/.claude/
|
||||
/ssl/
|
||||
.env
|
||||
.env.test
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and *not* Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
.bash_history
|
||||
.bashrc
|
||||
.init_done
|
||||
.profile
|
||||
.sudo_as_admin_successful
|
||||
.wget-hsts
|
||||
.git-credentials
|
||||
.docker/
|
||||
.bash_logout
|
||||
|
||||
# VSCode Files
|
||||
.vscode-server/
|
||||
|
||||
# Configs
|
||||
.config/
|
||||
|
||||
# .dotnet
|
||||
.dotnet/
|
||||
|
||||
# .local
|
||||
.local/
|
||||
/docker/docker-compose.yml
|
||||
/src/data/
|
||||
/docker/mongodb/
|
||||
/docker/docker-compose.yml
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
# Ignore artifacts:
|
||||
build
|
||||
coverage
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -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.
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
# Contributing
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- [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)
|
||||
|
||||
## Installation
|
||||
|
||||
1. Clone the repository:
|
||||
```sh
|
||||
git clone https://github.com/LukeGus/Termix
|
||||
```
|
||||
2. Install the dependencies:
|
||||
```sh
|
||||
npm install
|
||||
```
|
||||
|
||||
## Running the development server
|
||||
|
||||
Run the following commands:
|
||||
|
||||
```sh
|
||||
npm run dev
|
||||
npm run dev:backend
|
||||
```
|
||||
|
||||
This will start the backend and the frontend Vite server. You can access Termix by going to `http://localhost:5174/`.
|
||||
|
||||
## Contributing
|
||||
|
||||
1. **Fork the repository**: Click the "Fork" button at the top right of
|
||||
the [repository page](https://github.com/LukeGus/Termix).
|
||||
2. **Create a new branch**:
|
||||
```sh
|
||||
git checkout -b feature/my-new-feature
|
||||
```
|
||||
3. **Make your changes**: Implement your feature, fix, or improvement.
|
||||
4. **Commit your changes**:
|
||||
```sh
|
||||
git commit -m "Feature request my new feature"
|
||||
```
|
||||
5. **Push to your fork**:
|
||||
```sh
|
||||
git push origin feature/my-feature-request
|
||||
```
|
||||
6. **Open a pull request**: Go to the original repository and create a PR with a clear description.
|
||||
|
||||
## Guidelines
|
||||
|
||||
- Follow the existing code style. Use Tailwind CSS with shadcn components.
|
||||
- Use the below color scheme with the respective CSS variable placed in the `className` of a div/component.
|
||||
- Place all API routes in the `main-axios.ts` file. Updating the `openapi.json` is unneeded.
|
||||
- Include meaningful commit messages.
|
||||
- Link related issues when applicable.
|
||||
- `MobileApp.tsx` renders when the users screen width is less than 768px, otherwise it loads the usual `DesktopApp.tsx`.
|
||||
|
||||
## Color Scheme
|
||||
|
||||
### 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
|
||||
|
||||
If you need help with Termix, you can join the [Discord](https://discord.gg/jVQGdvHDrf) server and visit the support
|
||||
channel. You can also open an issue or open a pull request on the [GitHub](https://github.com/LukeGus/Termix/issues)
|
||||
repo.
|
||||
@@ -1,21 +1,13 @@
|
||||
MIT License
|
||||
Copyright 2025 Luke Gustafson
|
||||
|
||||
Copyright (c) 2025 Luke Gustafson
|
||||
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
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
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.
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
# 仓库统计
|
||||
|
||||
<p align="center">
|
||||
<a href="README.md"><img src="https://flagcdn.com/us.svg" alt="English" width="24" height="16"> 英文</a> |
|
||||
<img src="https://flagcdn.com/cn.svg" alt="中文" width="24" height="16"> 中文
|
||||
</p>
|
||||
|
||||

|
||||

|
||||

|
||||
<a href="https://discord.gg/jVQGdvHDrf"><img alt="Discord" src="https://img.shields.io/discord/1347374268253470720"></a>
|
||||
|
||||
<p align="center">
|
||||
<img src="./repo-images/RepoOfTheDay.png" alt="Repo of the Day Achievement" style="width: 300px; height: auto;">
|
||||
<br>
|
||||
<small style="color: #666;">2025年9月1日获得</small>
|
||||
</p>
|
||||
|
||||
#### 核心技术
|
||||
|
||||
[](#)
|
||||
[](#)
|
||||
[](#)
|
||||
[](#)
|
||||
[](#)
|
||||
[](#)
|
||||
[](#)
|
||||
[](#)
|
||||
|
||||
<br />
|
||||
<p align="center">
|
||||
<a href="https://github.com/LukeGus/Termix">
|
||||
<img alt="Termix Banner" src=./repo-images/HeaderImage.png style="width: auto; height: auto;"> </a>
|
||||
</p>
|
||||
|
||||
如果你愿意,可以在这里支持这个项目!\
|
||||
[](https://github.com/sponsors/LukeGus)
|
||||
|
||||
# 概览
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/LukeGus/Termix">
|
||||
<img alt="Termix Banner" src=./public/icon.svg style="width: 250px; height: 250px;"> </a>
|
||||
</p>
|
||||
|
||||
Termix 是一个开源、永久免费、自托管的一体化服务器管理平台。它提供了一个基于网页的解决方案,通过一个直观的界面管理你的服务器和基础设施。Termix
|
||||
提供 SSH 终端访问、SSH 隧道功能以及远程文件编辑,还会陆续添加更多工具。
|
||||
|
||||
# 功能
|
||||
|
||||
- **SSH 终端访问** - 功能完整的终端,支持分屏(最多 4 个面板)和标签系统
|
||||
- **SSH 隧道管理** - 创建和管理 SSH 隧道,支持自动重连和健康监控
|
||||
- **远程文件编辑器** - 直接在远程服务器编辑文件,支持语法高亮和文件管理功能(上传、删除、重命名等)
|
||||
- **SSH 主机管理器** - 保存、组织和管理 SSH 连接,支持标签和文件夹
|
||||
- **服务器统计** - 查看任意 SSH 服务器的 CPU、内存和硬盘使用情况
|
||||
- **用户认证** - 安全的用户管理,支持管理员控制、OIDC 和双因素认证(TOTP)
|
||||
- **现代化界面** - 使用 React、Tailwind CSS 和 Shadcn 构建的简洁界面
|
||||
- **语言支持** - 内置中英文支持
|
||||
|
||||
# 计划功能
|
||||
|
||||
- **增强管理员控制** - 提供更精细的用户和管理员权限控制、共享主机等功能
|
||||
- **主题定制** - 修改所有工具的主题风格
|
||||
- **增强终端支持** - 添加更多终端协议,如 VNC 和 RDP(有类似 Apache Guacamole 的 RDP 集成经验者请通过创建 issue 联系我)
|
||||
- **移动端支持** - 支持移动应用或 Termix 网站移动版,让你在手机上管理服务器
|
||||
|
||||
# 安装
|
||||
|
||||
访问 Termix [文档](https://docs.termix.site/install) 获取安装信息。或者可以参考以下示例 docker-compose 文件:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
termix:
|
||||
image: ghcr.io/lukegus/termix:latest
|
||||
container_name: termix
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "8080:8080"
|
||||
volumes:
|
||||
- termix-data:/app/data
|
||||
environment:
|
||||
PORT: "8080"
|
||||
|
||||
volumes:
|
||||
termix-data:
|
||||
driver: local
|
||||
```
|
||||
|
||||
# 支持
|
||||
|
||||
如果你需要 Termix 的帮助,可以加入 [Discord](https://discord.gg/jVQGdvHDrf)
|
||||
服务器并访问支持频道。你也可以在 [GitHub](https://github.com/LukeGus/Termix/issues) 仓库提交 issue 或 pull request。
|
||||
|
||||
# 展示
|
||||
|
||||
<p align="center">
|
||||
<img src="./repo-images/Image 1.png" width="400" alt="Termix Demo 1"/>
|
||||
<img src="./repo-images/Image 2.png" width="400" alt="Termix Demo 2"/>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="./repo-images/Image 3.png" width="250" alt="Termix Demo 3"/>
|
||||
<img src="./repo-images/Image 4.png" width="250" alt="Termix Demo 4"/>
|
||||
<img src="./repo-images/Image 5.png" width="250" alt="Termix Demo 5"/>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<video src="https://github.com/user-attachments/assets/f9caa061-10dc-4173-ae7d-c6d42f05cf56" width="800" controls>
|
||||
你的浏览器不支持 video 标签。
|
||||
</video>
|
||||
</p>
|
||||
|
||||
# 许可证
|
||||
|
||||
根据 Apache 2.0 许可证发布。更多信息请参见 LICENSE。
|
||||
@@ -1,62 +1,132 @@
|
||||
# Repo Stats
|
||||
|
||||
<p align="center">
|
||||
<img src="https://flagcdn.com/us.svg" alt="English" width="24" height="16"> English |
|
||||
<a href="README-CN.md"><img src="https://flagcdn.com/cn.svg" alt="中文" width="24" height="16"> 中文</a>
|
||||
</p>
|
||||
|
||||

|
||||

|
||||

|
||||
<a href="https://discord.gg/jVQGdvHDrf"><img alt="Discord" src="https://img.shields.io/discord/1347374268253470720"></a>
|
||||
|
||||
<p align="center">
|
||||
<img src="./repo-images/RepoOfTheDay.png" alt="Repo of the Day Achievement" style="width: 300px; height: auto;">
|
||||
<br>
|
||||
<small style="color: #666;">Achieved on September 1st, 2025</small>
|
||||
</p>
|
||||
|
||||
#### Top Technologies
|
||||
|
||||
[](#)
|
||||
[](#)
|
||||
[](#)
|
||||
[](#)
|
||||
[](#)
|
||||
[](#)
|
||||
[](#)
|
||||
[](#)
|
||||
[](#)
|
||||
[](#)
|
||||
[](#)
|
||||
|
||||
[](#)
|
||||
[](#)
|
||||
|
||||
<br />
|
||||
<p align="center">
|
||||
<a href="https://github.com/LukeGus/Termix">
|
||||
<img alt="Termix Banner" src=./repo-images/TermixLogo.png style="width: 125px; height: auto;"> </a>
|
||||
<img alt="Termix Banner" src=./repo-images/HeaderImage.png style="width: auto; height: auto;"> </a>
|
||||
</p>
|
||||
|
||||
If you would like, you can support the project here!\
|
||||
[](https://paypal.me/LukeGustafson803)
|
||||
[](https://github.com/sponsors/LukeGus)
|
||||
|
||||
# Overview
|
||||
Termix is an open-source forever free self-hosted SSH (other protocols planned, see [Planned Features](#planned-features)) server management panel inspired by [Nexterm](https://github.com/gnmyt/Nexterm). Its purpose is to provide an all-in-one docker-hosted web solution to manage your servers in one easy place. I'm using this project to help me learn [React](https://github.com/facebook/react), [Vite](https://github.com/vitejs/vite-plugin-react), and [Docker](https://www.docker.com) but also because I could never settle on a server management software that I enjoyed to use.
|
||||
|
||||
> [!WARNING]
|
||||
> This app is in the VERY early stages of development. Expect bugs, data loss, and unexplainable issues! For that reason, I recommend you securely tunnel your connection to Termix through a VPN.
|
||||
<p align="center">
|
||||
<a href="https://github.com/LukeGus/Termix">
|
||||
<img alt="Termix Banner" src=./public/icon.svg style="width: 250px; height: 250px;"> </a>
|
||||
</p>
|
||||
|
||||
Termix is an open-source, forever-free, self-hosted all-in-one server management platform. It provides a web-based
|
||||
solution for managing your servers and infrastructure through a single, intuitive interface. Termix offers SSH terminal
|
||||
access, SSH tunneling capabilities, remote file management, with many more tools to come.
|
||||
|
||||
# Features
|
||||
- SSH
|
||||
- Split Screen (Up to 4) & Tab System
|
||||
- User Authentication
|
||||
- Save Hosts (and easily view, connect, and manage them)
|
||||
|
||||
- **SSH Terminal Access** - Full-featured terminal with split-screen support (up to 4 panels) and tab system
|
||||
- **SSH Tunnel Management** - Create and manage SSH tunnels with automatic reconnection and health monitoring
|
||||
- **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.
|
||||
- **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 deploying of SSH keys
|
||||
- **Server Stats** - View CPU, memory, and HDD usage on any SSH server
|
||||
- **User Authentication** - Secure user management with admin controls and OIDC and 2FA (TOTP) support
|
||||
- **Database Encryption** - SQLite database files encrypted at rest with automatic encryption/decryption
|
||||
- **Data Export/Import** - Export and import SSH hosts, credentials, and file manager data with incremental sync
|
||||
- **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
|
||||
- **Languages** - Built-in support for English and Chinese
|
||||
- **Platform Support** - Available as a web app, desktop application (Windows & Linux), and dedicated mobile app for iOS and Android (coming in a few days)
|
||||
|
||||
# Planned Features
|
||||
- VNC
|
||||
- RDP
|
||||
- SFTP (build in file transfer)
|
||||
- ChatGPT/Ollama Integration (for commands)
|
||||
- Apps (like notes, AI, etc)
|
||||
- Terminal Themes
|
||||
- User Management (roles, permissions, etc.)
|
||||
- SSH Tunneling
|
||||
- More Authentication Methods
|
||||
- More Security Features (like 2FA, etc.)
|
||||
|
||||
See [Projects](https://github.com/users/LukeGus/projects/3) for all planned features. If you are looking to contribute, see [Contributing](https://github.com/LukeGus/Termix/blob/main/CONTRIBUTING.md).
|
||||
|
||||
# Installation
|
||||
Visit the Termix [Wiki](https://github.com/LukeGus/Termix/wiki) for information on how to install Termix. You can also use these links to go directly to guide. [Docker](https://github.com/LukeGus/Termix/wiki/Docker) or [Manual](https://github.com/LukeGus/Termix/wiki/Manual).
|
||||
|
||||
Supported Devices:
|
||||
|
||||
- Website (any modern browser like Google, Safari, and Firefox)
|
||||
- Windows (app)
|
||||
- Linux (app)
|
||||
- iOS (coming in a few days)
|
||||
- Android (coming in a few days)
|
||||
- iPadOS and macOS are in progress
|
||||
|
||||
Visit the Termix [Docs](https://docs.termix.site/install) for more information on how to install Termix on all platforms. Otherwise, view
|
||||
a sample docker-compose file here:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
termix:
|
||||
image: ghcr.io/lukegus/termix:latest
|
||||
container_name: termix
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "8080:8080"
|
||||
volumes:
|
||||
- termix-data:/app/data
|
||||
environment:
|
||||
PORT: "8080"
|
||||
|
||||
volumes:
|
||||
termix-data:
|
||||
driver: local
|
||||
```
|
||||
|
||||
# Support
|
||||
If you need help with Termix, you can join the [Discord](https://discord.gg/jVQGdvHDrf) server and visit the support channel. You can also open an issue or open a pull request on the [GitHub](https://github.com/LukeGus/Termix/issues) repo. If you would like to support me financially, you can on [Paypal](https://paypal.me/LukeGustafson803).
|
||||
|
||||
If you need help with Termix, you can join the [Discord](https://discord.gg/jVQGdvHDrf) server and visit the support
|
||||
channel. You can also open an issue or open a pull request on the [GitHub](https://github.com/LukeGus/Termix/issues)
|
||||
repo.
|
||||
|
||||
# Show-off
|
||||
|
||||

|
||||

|
||||
<p align="center">
|
||||
<img src="./repo-images/Image 1.png" width="400" alt="Termix Demo 1"/>
|
||||
<img src="./repo-images/Image 2.png" width="400" alt="Termix Demo 2"/>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="./repo-images/Image 3.png" width="400" alt="Termix Demo 3"/>
|
||||
<img src="./repo-images/Image 4.png" width="400" alt="Termix Demo 4"/>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="./repo-images/Image 5.png" width="400" alt="Termix Demo 5"/>
|
||||
<img src="./repo-images/Image 6.png" width="400" alt="Termix Demo 6"/>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<video src="https://github.com/user-attachments/assets/88936e0d-2399-4122-8eee-c255c25da48c" width="800" controls>
|
||||
Your browser does not support the video tag.
|
||||
</video>
|
||||
</p>
|
||||
|
||||
# License
|
||||
Distributed under the MIT license. See LICENSE for more information.
|
||||
|
||||
Distributed under the Apache License Version 2.0. See LICENSE for more information.
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# Security Policy
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Please report any vulnerabilities to [GitHub Security](https://github.com/LukeGus/Termix/security/advisories).
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "new-york",
|
||||
"rsc": false,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "",
|
||||
"css": "src/index.css",
|
||||
"baseColor": "zinc",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils",
|
||||
"ui": "@/components/ui",
|
||||
"lib": "@/lib",
|
||||
"hooks": "@/hooks"
|
||||
},
|
||||
"iconLibrary": "lucide"
|
||||
}
|
||||
+72
-90
@@ -1,106 +1,88 @@
|
||||
# Stage 1: Build frontend
|
||||
FROM node:18-alpine AS frontend-builder
|
||||
# Stage 1: Install dependencies
|
||||
FROM node:22-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 ./
|
||||
RUN npm ci --force && \
|
||||
|
||||
ENV npm_config_target_platform=linux
|
||||
ENV npm_config_target_arch=x64
|
||||
ENV npm_config_target_libc=glibc
|
||||
|
||||
RUN rm -rf node_modules package-lock.json && \
|
||||
npm install --force && \
|
||||
npm cache clean --force
|
||||
|
||||
# Stage 2: Build frontend
|
||||
FROM deps AS frontend-builder
|
||||
WORKDIR /app
|
||||
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
# Stage 2: Build backend
|
||||
FROM node:18-alpine AS backend-builder
|
||||
RUN find public/fonts -name "*.ttf" ! -name "*Regular.ttf" ! -name "*Bold.ttf" ! -name "*Italic.ttf" -delete
|
||||
|
||||
RUN npm cache clean --force && \
|
||||
npm run build
|
||||
|
||||
# Stage 3: Build backend
|
||||
FROM deps AS backend-builder
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
RUN npm ci --only=production --force && \
|
||||
npm cache clean --force
|
||||
COPY src/backend/ ./src/backend/
|
||||
|
||||
# Stage 3: Build bcrypt for Ubuntu
|
||||
FROM ubuntu:focal AS bcrypt-builder
|
||||
ENV DEBIAN_FRONTEND=noninteractive \
|
||||
NODE_VERSION=18.x
|
||||
COPY . .
|
||||
|
||||
ENV npm_config_target_platform=linux
|
||||
ENV npm_config_target_arch=x64
|
||||
ENV npm_config_target_libc=glibc
|
||||
|
||||
RUN npm rebuild better-sqlite3 --force
|
||||
|
||||
RUN npm run build:backend
|
||||
|
||||
# Stage 4: Production dependencies only
|
||||
FROM node:22-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 ./
|
||||
RUN rm -f /var/lib/apt/lists/lock /var/cache/apt/archives/lock /var/lib/dpkg/lock* && \
|
||||
apt-get clean && \
|
||||
apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
python3 \
|
||||
make \
|
||||
g++ \
|
||||
wget \
|
||||
ca-certificates \
|
||||
gnupg && \
|
||||
wget -qO- https://deb.nodesource.com/setup_${NODE_VERSION} | bash - && \
|
||||
apt-get update && \
|
||||
apt-get install -y nodejs && \
|
||||
npm ci --only=production bcrypt --force && \
|
||||
npm cache clean --force && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/* /var/cache/apt/* /tmp/*
|
||||
|
||||
# Final stage
|
||||
FROM ubuntu:focal
|
||||
ENV DEBIAN_FRONTEND=noninteractive \
|
||||
NODE_VERSION=18.x \
|
||||
MONGO_VERSION=4.4.24 \
|
||||
MONGO_URL=mongodb://localhost:27017/termix \
|
||||
MONGODB_DATA_DIR=/data/db \
|
||||
MONGODB_LOG_DIR=/var/log/mongodb \
|
||||
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
||||
ENV npm_config_target_platform=linux
|
||||
ENV npm_config_target_arch=x64
|
||||
ENV npm_config_target_libc=glibc
|
||||
|
||||
# Create users first
|
||||
RUN groupadd -r mongodb && useradd -r -g mongodb mongodb \
|
||||
&& groupadd -r node && useradd -r -g node -m node
|
||||
|
||||
# Install all dependencies in one layer
|
||||
RUN rm -f /var/lib/apt/lists/lock /var/cache/apt/archives/lock /var/lib/dpkg/lock* && \
|
||||
apt-get clean && \
|
||||
apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
ca-certificates \
|
||||
gnupg \
|
||||
gosu \
|
||||
nginx-light \
|
||||
wget && \
|
||||
# Add MongoDB 4.4 repository
|
||||
wget -qO - https://www.mongodb.org/static/pgp/server-4.4.asc | apt-key add - && \
|
||||
echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/4.4 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-4.4.list && \
|
||||
# Add MongoDB 5.0 repository
|
||||
wget -qO - https://www.mongodb.org/static/pgp/server-5.0.asc | apt-key add - && \
|
||||
echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/5.0 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-5.0.list && \
|
||||
# Add NodeJS repository
|
||||
wget -qO- https://deb.nodesource.com/setup_${NODE_VERSION} | bash - && \
|
||||
apt-get update && \
|
||||
# Install MongoDB 4.4 and 5.0 packages
|
||||
apt-get install -y --no-install-recommends \
|
||||
nodejs \
|
||||
mongodb-org-server=${MONGO_VERSION} \
|
||||
mongodb-org-shell=${MONGO_VERSION} \
|
||||
mongodb-org-server=5.0.21 && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/* /var/cache/apt/* /tmp/* && \
|
||||
mkdir -p /data/db /var/log/mongodb /var/run/mongodb && \
|
||||
chown -R mongodb:mongodb /data/db /var/log/mongodb /var/run/mongodb && \
|
||||
chmod 755 /data/db /var/log/mongodb /var/run/mongodb
|
||||
|
||||
# Setup nginx and frontend
|
||||
COPY docker/nginx.conf /etc/nginx/nginx.conf
|
||||
COPY --from=frontend-builder /app/dist /usr/share/nginx/html
|
||||
RUN chown -R www-data:www-data /usr/share/nginx/html
|
||||
|
||||
# Setup backend
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
RUN npm ci --only=production --ignore-scripts --force && \
|
||||
npm cache clean --force && \
|
||||
rm -rf /tmp/*
|
||||
COPY --from=bcrypt-builder /app/node_modules/bcrypt /app/node_modules/bcrypt
|
||||
COPY --from=backend-builder /app/src/backend ./src/backend
|
||||
RUN chown -R node:node /app
|
||||
npm rebuild better-sqlite3 bcryptjs --force && \
|
||||
npm cache clean --force
|
||||
|
||||
VOLUME ["/data/db"]
|
||||
EXPOSE 8080 8081 8082 27017
|
||||
# Stage 5: Final optimized image
|
||||
FROM node:22-slim
|
||||
WORKDIR /app
|
||||
|
||||
ENV DATA_DIR=/app/data \
|
||||
PORT=8080 \
|
||||
NODE_ENV=production
|
||||
|
||||
RUN apt-get update && apt-get install -y nginx gettext-base openssl && \
|
||||
rm -rf /var/lib/apt/lists/* && \
|
||||
mkdir -p /app/data /app/uploads && \
|
||||
chown -R node:node /app/data /app/uploads && \
|
||||
useradd -r -s /bin/false nginx
|
||||
|
||||
COPY docker/nginx.conf /etc/nginx/nginx.conf
|
||||
COPY docker/nginx-https.conf /etc/nginx/nginx-https.conf
|
||||
|
||||
COPY --chown=nginx:nginx --from=frontend-builder /app/dist /usr/share/nginx/html
|
||||
COPY --chown=nginx:nginx --from=frontend-builder /app/src/locales /usr/share/nginx/html/locales
|
||||
COPY --chown=nginx:nginx --from=frontend-builder /app/public/fonts /usr/share/nginx/html/fonts
|
||||
|
||||
COPY --chown=node:node --from=production-deps /app/node_modules /app/node_modules
|
||||
COPY --chown=node:node --from=backend-builder /app/dist/backend ./dist/backend
|
||||
COPY --chown=node:node package.json ./
|
||||
|
||||
VOLUME ["/app/data"]
|
||||
|
||||
EXPOSE ${PORT} 30001 30002 30003 30004 30005
|
||||
|
||||
COPY docker/entrypoint.sh /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh
|
||||
|
||||
+101
-104
@@ -1,124 +1,121 @@
|
||||
#!/bin/bash
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
# Create required directories and set permissions
|
||||
mkdir -p /data/db /var/log/mongodb /var/run/mongodb
|
||||
chown -R mongodb:mongodb /data/db /var/log/mongodb /var/run/mongodb
|
||||
chmod 755 /data/db /var/log/mongodb /var/run/mongodb
|
||||
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}
|
||||
|
||||
# Function to check MongoDB version
|
||||
check_mongo_version() {
|
||||
echo "Checking MongoDB version..."
|
||||
if [ -f "/data/db/diagnostic.data/metrics.2" ] || [ -f "/data/db/WiredTiger.wt" ]; then
|
||||
echo "Existing MongoDB data detected, attempting migration..."
|
||||
|
||||
# Clear any existing mongod lock file
|
||||
rm -f /tmp/mongodb-27017.sock
|
||||
rm -f /data/db/mongod.lock
|
||||
|
||||
# First, start MongoDB 5.0 to set compatibility version
|
||||
echo "Starting MongoDB 5.0 to set compatibility version..."
|
||||
gosu mongodb /usr/bin/mongod --dbpath $MONGODB_DATA_DIR --port 27017 --bind_ip 127.0.0.1 --config /etc/mongod.conf &
|
||||
MONGO_PID=$!
|
||||
|
||||
# Wait for MongoDB 5.0 to start
|
||||
echo "Waiting for MongoDB 5.0 to start..."
|
||||
MAX_TRIES=30
|
||||
COUNT=0
|
||||
while ! gosu mongodb mongo --quiet --eval "db.version()" > /dev/null 2>&1; do
|
||||
sleep 2
|
||||
COUNT=$((COUNT + 1))
|
||||
if [ $COUNT -ge $MAX_TRIES ]; then
|
||||
echo "Failed to start MongoDB 5.0 after $MAX_TRIES attempts"
|
||||
kill -9 $MONGO_PID 2>/dev/null || true
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
|
||||
# Set compatibility version to 4.4
|
||||
echo "Setting feature compatibility version to 4.4..."
|
||||
if ! gosu mongodb mongo --quiet --eval 'db.adminCommand({setFeatureCompatibilityVersion: "4.4"})'; then
|
||||
echo "Failed to set feature compatibility version"
|
||||
kill -9 $MONGO_PID 2>/dev/null || true
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Shutdown MongoDB 5.0 cleanly
|
||||
echo "Shutting down MongoDB 5.0..."
|
||||
gosu mongodb mongo --quiet --eval "db.adminCommand({shutdown: 1})" || kill $MONGO_PID
|
||||
|
||||
# Wait for process to end
|
||||
while kill -0 $MONGO_PID 2>/dev/null; do
|
||||
sleep 1
|
||||
done
|
||||
|
||||
# Run repair with MongoDB 4.4
|
||||
echo "Running repair with MongoDB 4.4..."
|
||||
gosu mongodb /usr/bin/mongod --dbpath $MONGODB_DATA_DIR --repair
|
||||
|
||||
return 0
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
echo "Configuring web UI to run on port: $PORT"
|
||||
|
||||
# Try migration up to 3 times
|
||||
MAX_MIGRATION_ATTEMPTS=3
|
||||
MIGRATION_ATTEMPT=1
|
||||
|
||||
while [ $MIGRATION_ATTEMPT -le $MAX_MIGRATION_ATTEMPTS ]; do
|
||||
echo "Migration attempt $MIGRATION_ATTEMPT of $MAX_MIGRATION_ATTEMPTS"
|
||||
if check_mongo_version; then
|
||||
break
|
||||
fi
|
||||
MIGRATION_ATTEMPT=$((MIGRATION_ATTEMPT + 1))
|
||||
if [ $MIGRATION_ATTEMPT -le $MAX_MIGRATION_ATTEMPTS ]; then
|
||||
echo "Migration failed, waiting before retry..."
|
||||
sleep 5
|
||||
fi
|
||||
done
|
||||
|
||||
if [ $MIGRATION_ATTEMPT -gt $MAX_MIGRATION_ATTEMPTS ]; then
|
||||
echo "Migration failed after $MAX_MIGRATION_ATTEMPTS attempts"
|
||||
exit 1
|
||||
if [ "$ENABLE_SSL" = "true" ]; then
|
||||
echo "SSL enabled - using HTTPS configuration with redirect"
|
||||
NGINX_CONF_SOURCE="/etc/nginx/nginx-https.conf"
|
||||
else
|
||||
echo "SSL disabled - using HTTP-only configuration (default)"
|
||||
NGINX_CONF_SOURCE="/etc/nginx/nginx.conf"
|
||||
fi
|
||||
|
||||
# Start MongoDB 4.4 normally
|
||||
echo "Starting MongoDB 4.4..."
|
||||
gosu mongodb /usr/bin/mongod --dbpath $MONGODB_DATA_DIR --logpath $MONGODB_LOG_DIR/mongodb.log --bind_ip 0.0.0.0 &
|
||||
MONGO_PID=$!
|
||||
envsubst '${PORT} ${SSL_PORT} ${SSL_CERT_PATH} ${SSL_KEY_PATH}' < $NGINX_CONF_SOURCE > /etc/nginx/nginx.conf.tmp
|
||||
mv /etc/nginx/nginx.conf.tmp /etc/nginx/nginx.conf
|
||||
|
||||
# Wait for MongoDB to be ready
|
||||
echo "Waiting for MongoDB to start..."
|
||||
MAX_TRIES=30
|
||||
COUNT=0
|
||||
while ! gosu mongodb mongo --quiet --eval "db.version()" > /dev/null 2>&1; do
|
||||
sleep 2
|
||||
COUNT=$((COUNT + 1))
|
||||
if [ $COUNT -ge $MAX_TRIES ]; then
|
||||
echo "Failed to start MongoDB. Checking logs:"
|
||||
cat $MONGODB_LOG_DIR/mongodb.log
|
||||
exit 1
|
||||
mkdir -p /app/data /app/uploads
|
||||
chown -R node:node /app/data /app/uploads
|
||||
chmod 755 /app/data /app/uploads
|
||||
|
||||
if [ "$ENABLE_SSL" = "true" ]; then
|
||||
echo "Checking SSL certificate configuration..."
|
||||
mkdir -p /app/data/ssl
|
||||
chown -R node:node /app/data/ssl
|
||||
chmod 755 /app/data/ssl
|
||||
|
||||
DOMAIN=${SSL_DOMAIN:-localhost}
|
||||
|
||||
if [ -f "/app/data/ssl/termix.crt" ] && [ -f "/app/data/ssl/termix.key" ]; then
|
||||
echo "SSL certificates found, checking validity..."
|
||||
|
||||
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
|
||||
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..."
|
||||
fi
|
||||
echo "Waiting for MongoDB... (attempt $COUNT/$MAX_TRIES)"
|
||||
done
|
||||
echo "MongoDB started successfully"
|
||||
|
||||
if [ ! -f "/app/data/ssl/termix.crt" ] || [ ! -f "/app/data/ssl/termix.key" ]; then
|
||||
echo "Generating SSL certificates for domain: $DOMAIN"
|
||||
|
||||
cat > /app/data/ssl/openssl.conf << EOF
|
||||
[req]
|
||||
default_bits = 2048
|
||||
prompt = no
|
||||
default_md = sha256
|
||||
distinguished_name = dn
|
||||
req_extensions = v3_req
|
||||
|
||||
[dn]
|
||||
C=US
|
||||
ST=State
|
||||
L=City
|
||||
O=Termix
|
||||
OU=IT Department
|
||||
CN=$DOMAIN
|
||||
|
||||
[v3_req]
|
||||
basicConstraints = CA:FALSE
|
||||
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
|
||||
subjectAltName = @alt_names
|
||||
|
||||
[alt_names]
|
||||
DNS.1 = $DOMAIN
|
||||
DNS.2 = localhost
|
||||
DNS.3 = 127.0.0.1
|
||||
IP.1 = 127.0.0.1
|
||||
IP.2 = ::1
|
||||
IP.3 = 0.0.0.0
|
||||
EOF
|
||||
|
||||
openssl genrsa -out /app/data/ssl/termix.key 2048
|
||||
|
||||
openssl req -new -x509 -key /app/data/ssl/termix.key -out /app/data/ssl/termix.crt -days 365 -config /app/data/ssl/openssl.conf -extensions v3_req
|
||||
|
||||
chmod 600 /app/data/ssl/termix.key
|
||||
chmod 644 /app/data/ssl/termix.crt
|
||||
chown node:node /app/data/ssl/termix.key /app/data/ssl/termix.crt
|
||||
|
||||
rm -f /app/data/ssl/openssl.conf
|
||||
|
||||
echo "SSL certificates generated successfully for domain: $DOMAIN"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Start nginx
|
||||
echo "Starting nginx..."
|
||||
nginx
|
||||
|
||||
# Start backend services
|
||||
echo "Starting backend services..."
|
||||
cd /app
|
||||
export NODE_ENV=production
|
||||
|
||||
# Start SSH service
|
||||
su -s /bin/bash node -c "node src/backend/ssh.cjs" &
|
||||
if [ -f "package.json" ]; then
|
||||
VERSION=$(grep '"version"' package.json | sed 's/.*"version": *"\([^"]*\)".*/\1/')
|
||||
if [ -n "$VERSION" ]; then
|
||||
export VERSION
|
||||
else
|
||||
echo "Warning: Could not extract version from package.json"
|
||||
fi
|
||||
else
|
||||
echo "Warning: package.json not found"
|
||||
fi
|
||||
|
||||
# Start database service
|
||||
su -s /bin/bash node -c "node src/backend/database.cjs" &
|
||||
if command -v su-exec > /dev/null 2>&1; then
|
||||
su-exec node node dist/backend/backend/starter.js
|
||||
else
|
||||
su -s /bin/sh node -c "node dist/backend/backend/starter.js"
|
||||
fi
|
||||
|
||||
echo "All services started"
|
||||
|
||||
# Keep container running and show logs
|
||||
tail -f $MONGODB_LOG_DIR/mongodb.log
|
||||
tail -f /dev/null
|
||||
@@ -0,0 +1,266 @@
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
include mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
sendfile on;
|
||||
keepalive_timeout 65;
|
||||
client_header_timeout 300s;
|
||||
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384;
|
||||
ssl_prefer_server_ciphers off;
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_session_timeout 10m;
|
||||
|
||||
server {
|
||||
listen ${PORT};
|
||||
server_name _;
|
||||
|
||||
return 301 https://$host:${SSL_PORT}$request_uri;
|
||||
}
|
||||
|
||||
# HTTPS Server
|
||||
server {
|
||||
listen ${SSL_PORT} ssl;
|
||||
server_name _;
|
||||
|
||||
ssl_certificate ${SSL_CERT_PATH};
|
||||
ssl_certificate_key ${SSL_KEY_PATH};
|
||||
|
||||
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||
add_header X-Frame-Options DENY always;
|
||||
add_header X-Content-Type-Options nosniff always;
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html index.htm;
|
||||
}
|
||||
|
||||
# Handle missing source map files gracefully
|
||||
location ~* \.map$ {
|
||||
return 404;
|
||||
access_log off;
|
||||
log_not_found off;
|
||||
}
|
||||
|
||||
location ~ ^/users(/.*)?$ {
|
||||
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 ~ ^/version(/.*)?$ {
|
||||
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 ~ ^/releases(/.*)?$ {
|
||||
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 ~ ^/alerts(/.*)?$ {
|
||||
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 ~ ^/credentials(/.*)?$ {
|
||||
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;
|
||||
|
||||
proxy_connect_timeout 60s;
|
||||
proxy_send_timeout 300s;
|
||||
proxy_read_timeout 300s;
|
||||
}
|
||||
|
||||
location ~ ^/database(/.*)?$ {
|
||||
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 $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 ~ ^/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 $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 ~ ^/encryption(/.*)?$ {
|
||||
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 /ssh/ {
|
||||
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 /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 $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_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 /ssh/tunnel/ {
|
||||
proxy_pass http://127.0.0.1:30003;
|
||||
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 /ssh/file_manager/recent {
|
||||
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 /ssh/file_manager/pinned {
|
||||
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 /ssh/file_manager/shortcuts {
|
||||
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 /ssh/file_manager/ssh/ {
|
||||
client_max_body_size 5G;
|
||||
client_body_timeout 300s;
|
||||
|
||||
proxy_pass http://127.0.0.1:30004;
|
||||
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;
|
||||
|
||||
proxy_connect_timeout 60s;
|
||||
proxy_send_timeout 300s;
|
||||
proxy_read_timeout 300s;
|
||||
|
||||
proxy_request_buffering off;
|
||||
proxy_buffering off;
|
||||
}
|
||||
|
||||
location /health {
|
||||
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 ~ ^/status(/.*)?$ {
|
||||
proxy_pass http://127.0.0.1:30005;
|
||||
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 ~ ^/metrics(/.*)?$ {
|
||||
proxy_pass http://127.0.0.1:30005;
|
||||
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;
|
||||
}
|
||||
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root /usr/share/nginx/html;
|
||||
}
|
||||
}
|
||||
}
|
||||
+221
-23
@@ -3,54 +3,252 @@ events {
|
||||
}
|
||||
|
||||
http {
|
||||
include mime.types;
|
||||
default_type application/octet-stream;
|
||||
include mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
sendfile on;
|
||||
keepalive_timeout 65;
|
||||
sendfile on;
|
||||
keepalive_timeout 65;
|
||||
client_header_timeout 300s;
|
||||
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384;
|
||||
ssl_prefer_server_ciphers off;
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_session_timeout 10m;
|
||||
|
||||
server {
|
||||
listen 8080;
|
||||
server_name localhost;
|
||||
listen ${PORT};
|
||||
server_name localhost;
|
||||
|
||||
add_header X-Frame-Options DENY always;
|
||||
add_header X-Content-Type-Options nosniff always;
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
|
||||
# Serve the React app
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html index.htm;
|
||||
root /usr/share/nginx/html;
|
||||
index index.html index.htm;
|
||||
}
|
||||
|
||||
# Proxy SSH socket requests
|
||||
location /ssh.io/ {
|
||||
proxy_pass http://127.0.0.1:8081;
|
||||
# Handle missing source map files gracefully
|
||||
location ~* \.map$ {
|
||||
return 404;
|
||||
access_log off;
|
||||
log_not_found off;
|
||||
}
|
||||
|
||||
location ~ ^/users(/.*)?$ {
|
||||
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 ~ ^/version(/.*)?$ {
|
||||
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 ~ ^/releases(/.*)?$ {
|
||||
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 ~ ^/alerts(/.*)?$ {
|
||||
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 ~ ^/credentials(/.*)?$ {
|
||||
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;
|
||||
|
||||
proxy_connect_timeout 60s;
|
||||
proxy_send_timeout 300s;
|
||||
proxy_read_timeout 300s;
|
||||
}
|
||||
|
||||
location ~ ^/database(/.*)?$ {
|
||||
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 $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 ~ ^/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 $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 ~ ^/encryption(/.*)?$ {
|
||||
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 /ssh/ {
|
||||
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 /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 Connection "upgrade";
|
||||
proxy_set_header Host $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_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;
|
||||
}
|
||||
|
||||
# Proxy MongoDB socket requests
|
||||
location /database.io/ {
|
||||
proxy_pass http://127.0.0.1:8082;
|
||||
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 $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;
|
||||
}
|
||||
|
||||
# Error pages
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location /ssh/file_manager/recent {
|
||||
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 /ssh/file_manager/pinned {
|
||||
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 /ssh/file_manager/shortcuts {
|
||||
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 /ssh/file_manager/ssh/ {
|
||||
client_max_body_size 5G;
|
||||
client_body_timeout 300s;
|
||||
|
||||
proxy_pass http://127.0.0.1:30004;
|
||||
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;
|
||||
|
||||
proxy_connect_timeout 60s;
|
||||
proxy_send_timeout 300s;
|
||||
proxy_read_timeout 300s;
|
||||
|
||||
proxy_request_buffering off;
|
||||
proxy_buffering off;
|
||||
}
|
||||
|
||||
location /health {
|
||||
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 ~ ^/status(/.*)?$ {
|
||||
proxy_pass http://127.0.0.1:30005;
|
||||
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 ~ ^/metrics(/.*)?$ {
|
||||
proxy_pass http://127.0.0.1:30005;
|
||||
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;
|
||||
}
|
||||
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root /usr/share/nginx/html;
|
||||
root /usr/share/nginx/html;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"appId": "com.termix.app",
|
||||
"productName": "Termix",
|
||||
"directories": {
|
||||
"output": "release"
|
||||
},
|
||||
"files": [
|
||||
"dist/**/*",
|
||||
"electron/**/*",
|
||||
"public/**/*",
|
||||
"!**/node_modules/**/*",
|
||||
"!src/**/*",
|
||||
"!*.md",
|
||||
"!tsconfig*.json",
|
||||
"!vite.config.ts",
|
||||
"!eslint.config.js"
|
||||
],
|
||||
"asarUnpack": ["node_modules/node-fetch/**/*"],
|
||||
"extraMetadata": {
|
||||
"main": "electron/main.cjs"
|
||||
},
|
||||
"buildDependenciesFromSource": false,
|
||||
"nodeGypRebuild": false,
|
||||
"npmRebuild": false,
|
||||
"win": {
|
||||
"target": "nsis",
|
||||
"icon": "public/icon.ico",
|
||||
"executableName": "Termix"
|
||||
},
|
||||
"nsis": {
|
||||
"oneClick": false,
|
||||
"allowToChangeInstallationDirectory": true,
|
||||
"artifactName": "${productName}-Setup-${version}.${ext}",
|
||||
"createDesktopShortcut": true,
|
||||
"createStartMenuShortcut": true,
|
||||
"shortcutName": "Termix",
|
||||
"uninstallDisplayName": "Termix"
|
||||
},
|
||||
"linux": {
|
||||
"target": [
|
||||
{
|
||||
"target": "AppImage",
|
||||
"arch": ["x64"]
|
||||
},
|
||||
{
|
||||
"target": "tar.gz",
|
||||
"arch": ["x64"]
|
||||
}
|
||||
],
|
||||
"icon": "public/icon.png",
|
||||
"category": "Development",
|
||||
"executableName": "termix",
|
||||
"desktop": {
|
||||
"entry": {
|
||||
"Name": "Termix",
|
||||
"Comment": "A web-based server management platform",
|
||||
"Keywords": "terminal;ssh;server;management;",
|
||||
"StartupWMClass": "termix"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,493 @@
|
||||
const { app, BrowserWindow, shell, ipcMain, dialog } = require("electron");
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
const os = require("os");
|
||||
|
||||
app.commandLine.appendSwitch("--ignore-certificate-errors");
|
||||
app.commandLine.appendSwitch("--ignore-ssl-errors");
|
||||
app.commandLine.appendSwitch("--ignore-certificate-errors-spki-list");
|
||||
app.commandLine.appendSwitch("--enable-features=NetworkService");
|
||||
|
||||
if (process.platform === "linux") {
|
||||
app.commandLine.appendSwitch("--no-sandbox");
|
||||
app.commandLine.appendSwitch("--disable-setuid-sandbox");
|
||||
app.commandLine.appendSwitch("--disable-dev-shm-usage");
|
||||
}
|
||||
|
||||
let mainWindow = null;
|
||||
|
||||
const isDev = process.env.NODE_ENV === "development" || !app.isPackaged;
|
||||
|
||||
const gotTheLock = app.requestSingleInstanceLock();
|
||||
if (!gotTheLock) {
|
||||
console.log("Another instance is already running, quitting...");
|
||||
app.quit();
|
||||
process.exit(0);
|
||||
} else {
|
||||
app.on("second-instance", (event, commandLine, workingDirectory) => {
|
||||
if (mainWindow) {
|
||||
if (mainWindow.isMinimized()) mainWindow.restore();
|
||||
mainWindow.focus();
|
||||
mainWindow.show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function createWindow() {
|
||||
mainWindow = new BrowserWindow({
|
||||
width: 1200,
|
||||
height: 800,
|
||||
minWidth: 800,
|
||||
minHeight: 600,
|
||||
title: "Termix",
|
||||
icon: isDev
|
||||
? path.join(__dirname, "..", "public", "icon.png")
|
||||
: path.join(process.resourcesPath, "public", "icon.png"),
|
||||
webPreferences: {
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
webSecurity: true,
|
||||
preload: path.join(__dirname, "preload.js"),
|
||||
},
|
||||
show: false,
|
||||
});
|
||||
|
||||
if (process.platform !== "darwin") {
|
||||
mainWindow.setMenuBarVisibility(false);
|
||||
}
|
||||
|
||||
if (isDev) {
|
||||
mainWindow.loadURL("http://localhost:5173");
|
||||
mainWindow.webContents.openDevTools();
|
||||
} else {
|
||||
const indexPath = path.join(__dirname, "..", "dist", "index.html");
|
||||
mainWindow.loadFile(indexPath);
|
||||
}
|
||||
|
||||
mainWindow.once("ready-to-show", () => {
|
||||
mainWindow.show();
|
||||
});
|
||||
|
||||
mainWindow.webContents.on(
|
||||
"did-fail-load",
|
||||
(event, errorCode, errorDescription, validatedURL) => {
|
||||
console.error(
|
||||
"Failed to load:",
|
||||
errorCode,
|
||||
errorDescription,
|
||||
validatedURL,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
mainWindow.webContents.on("did-finish-load", () => {
|
||||
console.log("Frontend loaded successfully");
|
||||
});
|
||||
|
||||
mainWindow.on("close", (event) => {
|
||||
if (process.platform === "darwin") {
|
||||
event.preventDefault();
|
||||
mainWindow.hide();
|
||||
}
|
||||
});
|
||||
|
||||
mainWindow.on("closed", () => {
|
||||
mainWindow = null;
|
||||
});
|
||||
|
||||
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
|
||||
shell.openExternal(url);
|
||||
return { action: "deny" };
|
||||
});
|
||||
}
|
||||
|
||||
ipcMain.handle("get-app-version", () => {
|
||||
return app.getVersion();
|
||||
});
|
||||
|
||||
const GITHUB_API_BASE = "https://api.github.com";
|
||||
const REPO_OWNER = "LukeGus";
|
||||
const REPO_NAME = "Termix";
|
||||
|
||||
const githubCache = new Map();
|
||||
const CACHE_DURATION = 30 * 60 * 1000; // 30 minutes
|
||||
|
||||
async function fetchGitHubAPI(endpoint, cacheKey) {
|
||||
const cached = githubCache.get(cacheKey);
|
||||
if (cached && Date.now() - cached.timestamp < CACHE_DURATION) {
|
||||
return {
|
||||
data: cached.data,
|
||||
cached: true,
|
||||
cache_age: Date.now() - cached.timestamp,
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
let fetch;
|
||||
try {
|
||||
fetch = globalThis.fetch || require("node-fetch");
|
||||
} catch (e) {
|
||||
const https = require("https");
|
||||
const http = require("http");
|
||||
const { URL } = require("url");
|
||||
|
||||
fetch = (url, options = {}) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const urlObj = new URL(url);
|
||||
const isHttps = urlObj.protocol === "https:";
|
||||
const client = isHttps ? https : http;
|
||||
|
||||
const requestOptions = {
|
||||
method: options.method || "GET",
|
||||
headers: options.headers || {},
|
||||
timeout: options.timeout || 10000,
|
||||
};
|
||||
|
||||
if (isHttps) {
|
||||
requestOptions.rejectUnauthorized = false;
|
||||
requestOptions.agent = new https.Agent({
|
||||
rejectUnauthorized: false,
|
||||
secureProtocol: "TLSv1_2_method",
|
||||
checkServerIdentity: () => undefined,
|
||||
ciphers: "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH",
|
||||
honorCipherOrder: true,
|
||||
});
|
||||
}
|
||||
|
||||
const req = client.request(url, requestOptions, (res) => {
|
||||
let data = "";
|
||||
res.on("data", (chunk) => (data += chunk));
|
||||
res.on("end", () => {
|
||||
resolve({
|
||||
ok: res.statusCode >= 200 && res.statusCode < 300,
|
||||
status: res.statusCode,
|
||||
text: () => Promise.resolve(data),
|
||||
json: () => Promise.resolve(JSON.parse(data)),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
req.on("error", reject);
|
||||
req.on("timeout", () => {
|
||||
req.destroy();
|
||||
reject(new Error("Request timeout"));
|
||||
});
|
||||
|
||||
if (options.body) {
|
||||
req.write(options.body);
|
||||
}
|
||||
req.end();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
const response = await fetch(`${GITHUB_API_BASE}${endpoint}`, {
|
||||
headers: {
|
||||
Accept: "application/vnd.github+json",
|
||||
"User-Agent": "TermixElectronUpdateChecker/1.0",
|
||||
"X-GitHub-Api-Version": "2022-11-28",
|
||||
},
|
||||
timeout: 10000,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`GitHub API error: ${response.status} ${response.statusText}`,
|
||||
);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
githubCache.set(cacheKey, {
|
||||
data,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
|
||||
return {
|
||||
data: data,
|
||||
cached: false,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch from GitHub API:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
ipcMain.handle("check-electron-update", async () => {
|
||||
try {
|
||||
const localVersion = app.getVersion();
|
||||
|
||||
const releaseData = await fetchGitHubAPI(
|
||||
`/repos/${REPO_OWNER}/${REPO_NAME}/releases/latest`,
|
||||
"latest_release_electron",
|
||||
);
|
||||
|
||||
const rawTag = releaseData.data.tag_name || releaseData.data.name || "";
|
||||
const remoteVersionMatch = rawTag.match(/(\d+\.\d+(\.\d+)?)/);
|
||||
const remoteVersion = remoteVersionMatch ? remoteVersionMatch[1] : null;
|
||||
|
||||
if (!remoteVersion) {
|
||||
return {
|
||||
success: false,
|
||||
error: "Remote version not found",
|
||||
localVersion,
|
||||
};
|
||||
}
|
||||
|
||||
const isUpToDate = localVersion === remoteVersion;
|
||||
|
||||
const result = {
|
||||
success: true,
|
||||
status: isUpToDate ? "up_to_date" : "requires_update",
|
||||
localVersion: localVersion,
|
||||
remoteVersion: remoteVersion,
|
||||
latest_release: {
|
||||
tag_name: releaseData.data.tag_name,
|
||||
name: releaseData.data.name,
|
||||
published_at: releaseData.data.published_at,
|
||||
html_url: releaseData.data.html_url,
|
||||
body: releaseData.data.body,
|
||||
},
|
||||
cached: releaseData.cached,
|
||||
cache_age: releaseData.cache_age,
|
||||
};
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
localVersion: app.getVersion(),
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle("get-platform", () => {
|
||||
return process.platform;
|
||||
});
|
||||
|
||||
ipcMain.handle("get-server-config", () => {
|
||||
try {
|
||||
const userDataPath = app.getPath("userData");
|
||||
const configPath = path.join(userDataPath, "server-config.json");
|
||||
|
||||
if (fs.existsSync(configPath)) {
|
||||
const configData = fs.readFileSync(configPath, "utf8");
|
||||
return JSON.parse(configData);
|
||||
}
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error("Error reading server config:", error);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle("save-server-config", (event, config) => {
|
||||
try {
|
||||
const userDataPath = app.getPath("userData");
|
||||
const configPath = path.join(userDataPath, "server-config.json");
|
||||
|
||||
if (!fs.existsSync(userDataPath)) {
|
||||
fs.mkdirSync(userDataPath, { recursive: true });
|
||||
}
|
||||
|
||||
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error("Error saving server config:", error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle("test-server-connection", async (event, serverUrl) => {
|
||||
try {
|
||||
const https = require("https");
|
||||
const http = require("http");
|
||||
const { URL } = require("url");
|
||||
|
||||
const fetch = (url, options = {}) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const urlObj = new URL(url);
|
||||
const isHttps = urlObj.protocol === "https:";
|
||||
const client = isHttps ? https : http;
|
||||
|
||||
const requestOptions = {
|
||||
method: options.method || "GET",
|
||||
headers: options.headers || {},
|
||||
timeout: options.timeout || 10000,
|
||||
};
|
||||
|
||||
if (isHttps) {
|
||||
requestOptions.rejectUnauthorized = false;
|
||||
requestOptions.agent = new https.Agent({
|
||||
rejectUnauthorized: false,
|
||||
secureProtocol: "TLSv1_2_method",
|
||||
checkServerIdentity: () => undefined,
|
||||
ciphers: "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH",
|
||||
honorCipherOrder: true,
|
||||
});
|
||||
}
|
||||
|
||||
const req = client.request(url, requestOptions, (res) => {
|
||||
let data = "";
|
||||
res.on("data", (chunk) => (data += chunk));
|
||||
res.on("end", () => {
|
||||
resolve({
|
||||
ok: res.statusCode >= 200 && res.statusCode < 300,
|
||||
status: res.statusCode,
|
||||
text: () => Promise.resolve(data),
|
||||
json: () => Promise.resolve(JSON.parse(data)),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
req.on("error", reject);
|
||||
req.on("timeout", () => {
|
||||
req.destroy();
|
||||
reject(new Error("Request timeout"));
|
||||
});
|
||||
|
||||
if (options.body) {
|
||||
req.write(options.body);
|
||||
}
|
||||
req.end();
|
||||
});
|
||||
};
|
||||
|
||||
const normalizedServerUrl = serverUrl.replace(/\/$/, "");
|
||||
|
||||
const healthUrl = `${normalizedServerUrl}/health`;
|
||||
|
||||
try {
|
||||
const response = await fetch(healthUrl, {
|
||||
method: "GET",
|
||||
timeout: 10000,
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.text();
|
||||
|
||||
if (
|
||||
data.includes("<html") ||
|
||||
data.includes("<!DOCTYPE") ||
|
||||
data.includes("<head>") ||
|
||||
data.includes("<body>")
|
||||
) {
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
"Server returned HTML instead of JSON. This does not appear to be a Termix server.",
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const healthData = JSON.parse(data);
|
||||
if (
|
||||
healthData &&
|
||||
(healthData.status === "ok" ||
|
||||
healthData.status === "healthy" ||
|
||||
healthData.healthy === true ||
|
||||
healthData.database === "connected")
|
||||
) {
|
||||
return {
|
||||
success: true,
|
||||
status: response.status,
|
||||
testedUrl: healthUrl,
|
||||
};
|
||||
}
|
||||
} catch (parseError) {
|
||||
console.log("Health endpoint did not return valid JSON");
|
||||
}
|
||||
}
|
||||
} catch (urlError) {
|
||||
console.error("Health check failed:", urlError);
|
||||
}
|
||||
|
||||
try {
|
||||
const versionUrl = `${normalizedServerUrl}/version`;
|
||||
const response = await fetch(versionUrl, {
|
||||
method: "GET",
|
||||
timeout: 10000,
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.text();
|
||||
|
||||
if (
|
||||
data.includes("<html") ||
|
||||
data.includes("<!DOCTYPE") ||
|
||||
data.includes("<head>") ||
|
||||
data.includes("<body>")
|
||||
) {
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
"Server returned HTML instead of JSON. This does not appear to be a Termix server.",
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const versionData = JSON.parse(data);
|
||||
if (
|
||||
versionData &&
|
||||
(versionData.status === "up_to_date" ||
|
||||
versionData.status === "requires_update" ||
|
||||
(versionData.localVersion &&
|
||||
versionData.version &&
|
||||
versionData.latest_release))
|
||||
) {
|
||||
return {
|
||||
success: true,
|
||||
status: response.status,
|
||||
testedUrl: versionUrl,
|
||||
warning:
|
||||
"Health endpoint not available, but server appears to be running",
|
||||
};
|
||||
}
|
||||
} catch (parseError) {
|
||||
console.log("Version endpoint did not return valid JSON");
|
||||
}
|
||||
}
|
||||
} catch (versionError) {
|
||||
console.error("Version check failed:", versionError);
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
"Server is not responding or does not appear to be a valid Termix server. Please ensure the server is running and accessible.",
|
||||
};
|
||||
} catch (error) {
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
});
|
||||
|
||||
app.whenReady().then(() => {
|
||||
createWindow();
|
||||
});
|
||||
|
||||
app.on("window-all-closed", () => {
|
||||
if (process.platform !== "darwin") {
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
|
||||
app.on("activate", () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow();
|
||||
} else if (mainWindow) {
|
||||
mainWindow.show();
|
||||
}
|
||||
});
|
||||
|
||||
app.on("will-quit", () => {
|
||||
console.log("App will quit...");
|
||||
});
|
||||
|
||||
process.on("uncaughtException", (error) => {
|
||||
console.error("Uncaught Exception:", error);
|
||||
});
|
||||
|
||||
process.on("unhandledRejection", (reason, promise) => {
|
||||
console.error("Unhandled Rejection at:", promise, "reason:", reason);
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
const { contextBridge, ipcRenderer } = require("electron");
|
||||
|
||||
contextBridge.exposeInMainWorld("electronAPI", {
|
||||
getAppVersion: () => ipcRenderer.invoke("get-app-version"),
|
||||
getPlatform: () => ipcRenderer.invoke("get-platform"),
|
||||
checkElectronUpdate: () => ipcRenderer.invoke("check-electron-update"),
|
||||
|
||||
getServerConfig: () => ipcRenderer.invoke("get-server-config"),
|
||||
saveServerConfig: (config) =>
|
||||
ipcRenderer.invoke("save-server-config", config),
|
||||
testServerConnection: (serverUrl) =>
|
||||
ipcRenderer.invoke("test-server-connection", serverUrl),
|
||||
|
||||
showSaveDialog: (options) => ipcRenderer.invoke("show-save-dialog", options),
|
||||
showOpenDialog: (options) => ipcRenderer.invoke("show-open-dialog", options),
|
||||
|
||||
onUpdateAvailable: (callback) => ipcRenderer.on("update-available", callback),
|
||||
onUpdateDownloaded: (callback) =>
|
||||
ipcRenderer.on("update-downloaded", callback),
|
||||
|
||||
removeAllListeners: (channel) => ipcRenderer.removeAllListeners(channel),
|
||||
isElectron: true,
|
||||
isDev: process.env.NODE_ENV === "development",
|
||||
|
||||
invoke: (channel, ...args) => ipcRenderer.invoke(channel, ...args),
|
||||
});
|
||||
|
||||
window.IS_ELECTRON = true;
|
||||
+16
-31
@@ -1,38 +1,23 @@
|
||||
import js from '@eslint/js'
|
||||
import globals from 'globals'
|
||||
import react from 'eslint-plugin-react'
|
||||
import reactHooks from 'eslint-plugin-react-hooks'
|
||||
import reactRefresh from 'eslint-plugin-react-refresh'
|
||||
import js from "@eslint/js";
|
||||
import globals from "globals";
|
||||
import reactHooks from "eslint-plugin-react-hooks";
|
||||
import reactRefresh from "eslint-plugin-react-refresh";
|
||||
import tseslint from "typescript-eslint";
|
||||
import { globalIgnores } from "eslint/config";
|
||||
|
||||
export default [
|
||||
{ ignores: ['dist'] },
|
||||
export default tseslint.config([
|
||||
globalIgnores(["dist"]),
|
||||
{
|
||||
files: ['**/*.{js,jsx}'],
|
||||
files: ["**/*.{ts,tsx}"],
|
||||
extends: [
|
||||
js.configs.recommended,
|
||||
tseslint.configs.recommended,
|
||||
reactHooks.configs["recommended-latest"],
|
||||
reactRefresh.configs.vite,
|
||||
],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
globals: globals.browser,
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
ecmaFeatures: { jsx: true },
|
||||
sourceType: 'module',
|
||||
},
|
||||
},
|
||||
settings: { react: { version: '18.3' } },
|
||||
plugins: {
|
||||
react,
|
||||
'react-hooks': reactHooks,
|
||||
'react-refresh': reactRefresh,
|
||||
},
|
||||
rules: {
|
||||
...js.configs.recommended.rules,
|
||||
...react.configs.recommended.rules,
|
||||
...react.configs['jsx-runtime'].rules,
|
||||
...reactHooks.configs.recommended.rules,
|
||||
'react/jsx-no-target-blank': 'off',
|
||||
'react-refresh/only-export-components': [
|
||||
'warn',
|
||||
{ allowConstantExport: true },
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
]);
|
||||
|
||||
+1
-1
@@ -8,6 +8,6 @@
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.jsx"></script>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+2305
File diff suppressed because it is too large
Load Diff
Generated
+12281
-4896
File diff suppressed because it is too large
Load Diff
+115
-45
@@ -1,61 +1,131 @@
|
||||
{
|
||||
"name": "termix",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"version": "1.7.1",
|
||||
"description": "A web-based server management platform with SSH terminal, tunneling, and file editing capabilities",
|
||||
"author": "Karmaa",
|
||||
"main": "electron/main.cjs",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"clean": "npx prettier . --write",
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview"
|
||||
"build": "vite build && tsc -p tsconfig.node.json",
|
||||
"build:backend": "tsc -p tsconfig.node.json",
|
||||
"dev:backend": "tsc -p tsconfig.node.json && node ./dist/backend/backend/starter.js",
|
||||
"preview": "vite preview",
|
||||
"electron:dev": "concurrently \"npm run dev\" \"wait-on http://localhost:5173 && electron .\"",
|
||||
"build:win-portable": "npm run build && electron-builder --win --dir",
|
||||
"build:win-installer": "npm run build && electron-builder --win --publish=never",
|
||||
"build:linux-portable": "npm run build && electron-builder --linux --dir",
|
||||
"build:linux-appimage": "npm run build && electron-builder --linux AppImage",
|
||||
"build:linux-targz": "npm run build && electron-builder --linux tar.gz",
|
||||
"test:encryption": "tsc -p tsconfig.node.json && node ./dist/backend/backend/utils/encryption-test.js",
|
||||
"migrate:encryption": "tsc -p tsconfig.node.json && node ./dist/backend/backend/utils/encryption-migration.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.0",
|
||||
"@fontsource/inter": "^5.1.1",
|
||||
"@mui/icons-material": "^6.4.7",
|
||||
"@mui/joy": "^5.0.0-beta.51",
|
||||
"@tailwindcss/vite": "^4.0.15",
|
||||
"@tiptap/extension-link": "^2.11.5",
|
||||
"@tiptap/pm": "^2.11.5",
|
||||
"@tiptap/react": "^2.11.5",
|
||||
"@tiptap/starter-kit": "^2.11.5",
|
||||
"@codemirror/autocomplete": "^6.18.7",
|
||||
"@codemirror/commands": "^6.3.3",
|
||||
"@codemirror/search": "^6.5.11",
|
||||
"@codemirror/theme-one-dark": "^6.1.2",
|
||||
"@codemirror/view": "^6.23.1",
|
||||
"@hookform/resolvers": "^5.1.1",
|
||||
"@monaco-editor/react": "^4.7.0",
|
||||
"@radix-ui/react-accordion": "^1.2.11",
|
||||
"@radix-ui/react-checkbox": "^1.3.2",
|
||||
"@radix-ui/react-dialog": "^1.1.15",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.15",
|
||||
"@radix-ui/react-label": "^2.1.7",
|
||||
"@radix-ui/react-popover": "^1.1.14",
|
||||
"@radix-ui/react-progress": "^1.1.7",
|
||||
"@radix-ui/react-scroll-area": "^1.2.9",
|
||||
"@radix-ui/react-select": "^2.2.5",
|
||||
"@radix-ui/react-separator": "^1.1.7",
|
||||
"@radix-ui/react-slot": "^1.2.3",
|
||||
"@radix-ui/react-switch": "^1.2.5",
|
||||
"@radix-ui/react-tabs": "^1.1.12",
|
||||
"@radix-ui/react-tooltip": "^1.2.8",
|
||||
"@tailwindcss/vite": "^4.1.11",
|
||||
"@types/bcryptjs": "^2.4.6",
|
||||
"@types/cookie-parser": "^1.4.9",
|
||||
"@types/jszip": "^3.4.0",
|
||||
"@types/multer": "^2.0.0",
|
||||
"@types/qrcode": "^1.5.5",
|
||||
"@types/speakeasy": "^2.0.10",
|
||||
"@uiw/codemirror-extensions-langs": "^4.24.1",
|
||||
"@uiw/react-codemirror": "^4.24.1",
|
||||
"@xterm/addon-clipboard": "^0.1.0",
|
||||
"@xterm/addon-fit": "^0.10.0",
|
||||
"@xterm/addon-unicode11": "^0.8.0",
|
||||
"@xterm/addon-web-links": "^0.11.0",
|
||||
"@xterm/xterm": "^5.5.0",
|
||||
"bcrypt": "^5.1.1",
|
||||
"axios": "^1.10.0",
|
||||
"bcryptjs": "^3.0.2",
|
||||
"better-sqlite3": "^12.2.0",
|
||||
"body-parser": "^1.20.2",
|
||||
"chalk": "^4.1.2",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"cors": "^2.8.5",
|
||||
"crypto": "^1.0.1",
|
||||
"dayjs": "^1.11.13",
|
||||
"dotenv": "^16.4.7",
|
||||
"embla-carousel-react": "^7.1.0",
|
||||
"express": "^4.21.2",
|
||||
"is-stream": "^4.0.1",
|
||||
"make-dir": "^5.0.0",
|
||||
"mitt": "^3.0.1",
|
||||
"mongoose": "^8.12.1",
|
||||
"node-ssh": "^13.2.0",
|
||||
"prop-types": "^15.8.1",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"recharts": "^2.15.1",
|
||||
"sb-promise-queue": "^2.1.1",
|
||||
"sb-scandir": "^3.1.0",
|
||||
"shell-escape": "^0.2.0",
|
||||
"socket.io": "^4.8.1",
|
||||
"socket.io-client": "^4.8.1",
|
||||
"dotenv": "^17.2.0",
|
||||
"drizzle-orm": "^0.44.3",
|
||||
"express": "^5.1.0",
|
||||
"i18next": "^25.4.2",
|
||||
"i18next-browser-languagedetector": "^8.2.0",
|
||||
"jose": "^5.2.3",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"jszip": "^3.10.1",
|
||||
"lucide-react": "^0.525.0",
|
||||
"multer": "^2.0.2",
|
||||
"nanoid": "^5.1.5",
|
||||
"next-themes": "^0.4.6",
|
||||
"node-fetch": "^3.3.2",
|
||||
"qrcode": "^1.5.4",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-h5-audio-player": "^3.10.1",
|
||||
"react-hook-form": "^7.60.0",
|
||||
"react-i18next": "^15.7.3",
|
||||
"react-icons": "^5.5.0",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-pdf": "^10.1.0",
|
||||
"react-photo-view": "^1.2.7",
|
||||
"react-player": "^3.3.3",
|
||||
"react-resizable-panels": "^3.0.3",
|
||||
"react-simple-keyboard": "^3.8.120",
|
||||
"react-syntax-highlighter": "^15.6.6",
|
||||
"react-xtermjs": "^1.0.10",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"sonner": "^2.0.7",
|
||||
"speakeasy": "^2.0.0",
|
||||
"ssh2": "^1.16.0",
|
||||
"tailwindcss": "^4.0.15"
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"wait-on": "^9.0.1",
|
||||
"ws": "^8.18.3",
|
||||
"zod": "^4.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.17.0",
|
||||
"@types/react": "^18.3.18",
|
||||
"@types/react-dom": "^18.3.5",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"eslint": "^9.17.0",
|
||||
"eslint-plugin-react": "^7.37.2",
|
||||
"eslint-plugin-react-hooks": "^5.0.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.16",
|
||||
"globals": "^15.14.0",
|
||||
"vite": "^6.0.5"
|
||||
"@eslint/js": "^9.34.0",
|
||||
"@types/better-sqlite3": "^7.6.13",
|
||||
"@types/cors": "^2.8.19",
|
||||
"@types/express": "^5.0.3",
|
||||
"@types/jsonwebtoken": "^9.0.10",
|
||||
"@types/node": "^24.3.0",
|
||||
"@types/react": "^19.1.8",
|
||||
"@types/react-dom": "^19.1.6",
|
||||
"@types/ssh2": "^1.15.5",
|
||||
"@types/ws": "^8.18.1",
|
||||
"@vitejs/plugin-react-swc": "^3.10.2",
|
||||
"concurrently": "^9.2.1",
|
||||
"electron": "^38.0.0",
|
||||
"electron-builder": "^26.0.12",
|
||||
"eslint": "^9.34.0",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.20",
|
||||
"globals": "^16.3.0",
|
||||
"prettier": "3.6.2",
|
||||
"typescript": "~5.9.2",
|
||||
"typescript-eslint": "^8.40.0",
|
||||
"vite": "^7.1.5"
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 168 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user