ssh! и документы

This commit is contained in:
Georgiy Syralev
2025-10-12 15:20:39 +03:00
parent 141955dd46
commit 4211692312
50 changed files with 920 additions and 1333 deletions

View File

@@ -10,6 +10,8 @@
"license": "ISC",
"dependencies": {
"@prisma/client": "^6.16.2",
"@types/ssh2": "^1.15.5",
"@types/ws": "^8.18.1",
"axios": "^1.12.2",
"bcrypt": "^6.0.0",
"bcryptjs": "^2.4.3",
@@ -18,8 +20,10 @@
"express": "^4.21.2",
"jsonwebtoken": "^9.0.2",
"multer": "^2.0.2",
"nodemailer": "^6.9.16",
"socket.io": "^4.8.1"
"proxmox-api": "^1.1.1",
"ssh2": "^1.17.0",
"ws": "^8.18.3",
"xterm": "^5.3.0"
},
"devDependencies": {
"@types/bcrypt": "^6.0.0",
@@ -29,7 +33,7 @@
"@types/jsonwebtoken": "^9.0.10",
"@types/multer": "^2.0.0",
"@types/node": "^20.12.12",
"@types/nodemailer": "^6.4.17",
"@types/xterm": "^2.0.3",
"prisma": "^6.16.2",
"ts-node-dev": "^2.0.0",
"typescript": "^5.4.5"
@@ -161,12 +165,6 @@
"@prisma/debug": "6.16.2"
}
},
"node_modules/@socket.io/component-emitter": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
"integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==",
"license": "MIT"
},
"node_modules/@standard-schema/spec": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz",
@@ -244,6 +242,7 @@
"version": "2.8.19",
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz",
"integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
@@ -326,16 +325,6 @@
"undici-types": "~6.21.0"
}
},
"node_modules/@types/nodemailer": {
"version": "6.4.17",
"resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.17.tgz",
"integrity": "sha512-I9CCaIp6DTldEg7vyUTZi8+9Vo0hi1/T8gv3C89yk1rSAAzoKQ8H8ki/jBYJSFoH/BisgLP8tkZMlQ91CIquww==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/qs": {
"version": "6.14.0",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz",
@@ -373,6 +362,30 @@
"@types/send": "*"
}
},
"node_modules/@types/ssh2": {
"version": "1.15.5",
"resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-1.15.5.tgz",
"integrity": "sha512-N1ASjp/nXH3ovBHddRJpli4ozpk6UdDYIX4RJWFa9L1YKnzdhTlVmiGHm4DZnj/jLbqZpes4aeR30EFGQtvhQQ==",
"license": "MIT",
"dependencies": {
"@types/node": "^18.11.18"
}
},
"node_modules/@types/ssh2/node_modules/@types/node": {
"version": "18.19.127",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.127.tgz",
"integrity": "sha512-gSjxjrnKXML/yo0BO099uPixMqfpJU0TKYjpfLU7TrtA2WWDki412Np/RSTPRil1saKBhvVVKzVx/p/6p94nVA==",
"license": "MIT",
"dependencies": {
"undici-types": "~5.26.4"
}
},
"node_modules/@types/ssh2/node_modules/undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"license": "MIT"
},
"node_modules/@types/strip-bom": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz",
@@ -387,6 +400,22 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/ws": {
"version": "8.18.1",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
"integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/xterm": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@types/xterm/-/xterm-2.0.3.tgz",
"integrity": "sha512-Owlz29ThHtn2RQry87juaNYeIc4Dr8ykLLX0JKKt4SdO6ujwJnsXCpBAr6bwo/f4L3xSfM9KA7OnPPf9Xit6tA==",
"dev": true,
"license": "MIT"
},
"node_modules/accepts": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
@@ -459,6 +488,15 @@
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
"license": "MIT"
},
"node_modules/asn1": {
"version": "0.2.6",
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
"integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==",
"license": "MIT",
"dependencies": {
"safer-buffer": "~2.1.0"
}
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@@ -483,15 +521,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/base64id": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
"integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==",
"license": "MIT",
"engines": {
"node": "^4.5.0 || >= 5.9"
}
},
"node_modules/bcrypt": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-6.0.0.tgz",
@@ -506,6 +535,15 @@
"node": ">= 18"
}
},
"node_modules/bcrypt-pbkdf": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
"integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==",
"license": "BSD-3-Clause",
"dependencies": {
"tweetnacl": "^0.14.3"
}
},
"node_modules/bcryptjs": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
@@ -585,6 +623,15 @@
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
"license": "MIT"
},
"node_modules/buildcheck": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/buildcheck/-/buildcheck-0.0.6.tgz",
"integrity": "sha512-8f9ZJCUXyT1M35Jx7MkBgmBMo3oHTTBIPLiY9xyL0pl3T5RwcPEY8cUHr5LBNfu/fk6c2T4DJZuVM/8ZZT2D2A==",
"optional": true,
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/busboy": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
@@ -789,6 +836,20 @@
"node": ">= 0.10"
}
},
"node_modules/cpu-features": {
"version": "0.0.10",
"resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.10.tgz",
"integrity": "sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==",
"hasInstallScript": true,
"optional": true,
"dependencies": {
"buildcheck": "~0.0.6",
"nan": "^2.19.0"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/create-require": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
@@ -948,67 +1009,6 @@
"node": ">= 0.8"
}
},
"node_modules/engine.io": {
"version": "6.6.4",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz",
"integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==",
"license": "MIT",
"dependencies": {
"@types/cors": "^2.8.12",
"@types/node": ">=10.0.0",
"accepts": "~1.3.4",
"base64id": "2.0.0",
"cookie": "~0.7.2",
"cors": "~2.8.5",
"debug": "~4.3.1",
"engine.io-parser": "~5.2.1",
"ws": "~8.17.1"
},
"engines": {
"node": ">=10.2.0"
}
},
"node_modules/engine.io-parser": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
"integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/engine.io/node_modules/cookie": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/engine.io/node_modules/debug": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/engine.io/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
@@ -1767,6 +1767,13 @@
"mkdirp": "bin/cmd.js"
}
},
"node_modules/nan": {
"version": "2.23.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.23.0.tgz",
"integrity": "sha512-1UxuyYGdoQHcGg87Lkqm3FzefucTa0NAiOcuRsDmysep3c1LVCRK2krrUDafMWtjSG04htvAmvg96+SDknOmgQ==",
"license": "MIT",
"optional": true
},
"node_modules/negotiator": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
@@ -1803,15 +1810,6 @@
"node-gyp-build-test": "build-test.js"
}
},
"node_modules/nodemailer": {
"version": "6.9.16",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.16.tgz",
"integrity": "sha512-psAuZdTIRN08HKVd/E8ObdV6NO7NTBY3KsC30F7M4H1OnmLCUNaS56FpYxyb26zWLSyYF9Ozch9KYHhHegsiOQ==",
"license": "MIT-0",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
@@ -1989,6 +1987,18 @@
}
}
},
"node_modules/proxmox-api": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/proxmox-api/-/proxmox-api-1.1.1.tgz",
"integrity": "sha512-2qH7pxKBBHa7WtEBmxPaBY2FZEH2R04hqr9zD9PmErLzJ7RGGcfNcXoS/v5G4vBM2Igmnx0EAYBstPwwfDwHnA==",
"license": "GPL-3.0",
"dependencies": {
"undici": "^6.19.8"
},
"funding": {
"url": "https://github.com/sponsors/urielch"
}
},
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@@ -2308,116 +2318,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/socket.io": {
"version": "4.8.1",
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz",
"integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==",
"license": "MIT",
"dependencies": {
"accepts": "~1.3.4",
"base64id": "~2.0.0",
"cors": "~2.8.5",
"debug": "~4.3.2",
"engine.io": "~6.6.0",
"socket.io-adapter": "~2.5.2",
"socket.io-parser": "~4.2.4"
},
"engines": {
"node": ">=10.2.0"
}
},
"node_modules/socket.io-adapter": {
"version": "2.5.5",
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz",
"integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==",
"license": "MIT",
"dependencies": {
"debug": "~4.3.4",
"ws": "~8.17.1"
}
},
"node_modules/socket.io-adapter/node_modules/debug": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/socket.io-adapter/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/socket.io-parser": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
"integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
"license": "MIT",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/socket.io-parser/node_modules/debug": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/socket.io-parser/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/socket.io/node_modules/debug": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/socket.io/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@@ -2439,6 +2339,23 @@
"source-map": "^0.6.0"
}
},
"node_modules/ssh2": {
"version": "1.17.0",
"resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.17.0.tgz",
"integrity": "sha512-wPldCk3asibAjQ/kziWQQt1Wh3PgDFpC0XpwclzKcdT1vql6KeYxf5LIt4nlFkUeR8WuphYMKqUA56X4rjbfgQ==",
"hasInstallScript": true,
"dependencies": {
"asn1": "^0.2.6",
"bcrypt-pbkdf": "^1.0.2"
},
"engines": {
"node": ">=10.16.0"
},
"optionalDependencies": {
"cpu-features": "~0.0.10",
"nan": "^2.23.0"
}
},
"node_modules/statuses": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
@@ -2667,6 +2584,12 @@
"strip-json-comments": "^2.0.0"
}
},
"node_modules/tweetnacl": {
"version": "0.14.5",
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
"integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==",
"license": "Unlicense"
},
"node_modules/type-is": {
"version": "1.6.18",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
@@ -2700,6 +2623,15 @@
"node": ">=14.17"
}
},
"node_modules/undici": {
"version": "6.22.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.22.0.tgz",
"integrity": "sha512-hU/10obOIu62MGYjdskASR3CUAiYaFTtC9Pa6vHyf//mAipSvSQg6od2CnJswq7fvzNS3zJhxoRkgNVaHurWKw==",
"license": "MIT",
"engines": {
"node": ">=18.17"
}
},
"node_modules/undici-types": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
@@ -2754,9 +2686,9 @@
"license": "ISC"
},
"node_modules/ws": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
"version": "8.18.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
@@ -2783,6 +2715,13 @@
"node": ">=0.4"
}
},
"node_modules/xterm": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/xterm/-/xterm-5.3.0.tgz",
"integrity": "sha512-8QqjlekLUFTrU6x7xck1MsPzPA571K5zNqWm0M0oroYEWVOptZ0+ubQSkQ3uxIEhcIHRujJy6emDWX4A7qyFzg==",
"deprecated": "This package is now deprecated. Move to @xterm/xterm instead.",
"license": "MIT"
},
"node_modules/yn": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",

View File

@@ -5,7 +5,7 @@
"main": "dist/index.js",
"scripts": {
"dev": "ts-node-dev --respawn --transpile-only src/index.ts",
"start": "node dist/index.js",
"start": "node dist/src/index.js",
"build": "tsc"
},
"keywords": [],
@@ -13,6 +13,8 @@
"license": "ISC",
"dependencies": {
"@prisma/client": "^6.16.2",
"@types/ssh2": "^1.15.5",
"@types/ws": "^8.18.1",
"axios": "^1.12.2",
"bcrypt": "^6.0.0",
"bcryptjs": "^2.4.3",
@@ -21,8 +23,10 @@
"express": "^4.21.2",
"jsonwebtoken": "^9.0.2",
"multer": "^2.0.2",
"nodemailer": "^6.9.16",
"socket.io": "^4.8.1"
"proxmox-api": "^1.1.1",
"ssh2": "^1.17.0",
"ws": "^8.18.3",
"xterm": "^5.3.0"
},
"devDependencies": {
"@types/bcrypt": "^6.0.0",
@@ -32,7 +36,7 @@
"@types/jsonwebtoken": "^9.0.10",
"@types/multer": "^2.0.0",
"@types/node": "^20.12.12",
"@types/nodemailer": "^6.4.17",
"@types/xterm": "^2.0.3",
"prisma": "^6.16.2",
"ts-node-dev": "^2.0.0",
"typescript": "^5.4.5"

View File

@@ -17,8 +17,12 @@ model Tariff {
description String?
createdAt DateTime @default(now())
servers Server[]
@@map("tariff")
}
model OperatingSystem {
id Int @id @default(autoincrement())
name String @unique
@@ -26,6 +30,8 @@ model OperatingSystem {
template String? // путь к шаблону для контейнера
createdAt DateTime @default(now())
servers Server[]
@@map("operatingsystem")
}
model Server {
@@ -60,6 +66,8 @@ model Server {
diskUsage Float? @default(0)
networkIn Float? @default(0)
networkOut Float? @default(0)
@@map("server")
}
model User {
@@ -76,6 +84,8 @@ model User {
balance Float @default(0)
servers Server[]
notifications Notification[]
@@map("user")
}
model Check {
@@ -86,6 +96,8 @@ model Check {
fileUrl String
createdAt DateTime @default(now())
user User @relation("UserChecks", fields: [userId], references: [id])
@@map("check")
}
model Plan {
@@ -98,6 +110,8 @@ model Plan {
userId Int
owner User @relation("UserPlans", fields: [userId], references: [id])
services Service[] @relation("PlanServices")
@@map("plan")
}
model Service {
@@ -106,6 +120,8 @@ model Service {
price Float
planId Int?
plan Plan? @relation("PlanServices", fields: [planId], references: [id])
@@map("service")
}
model Ticket {
@@ -118,6 +134,8 @@ model Ticket {
updatedAt DateTime @updatedAt
responses Response[] @relation("TicketResponses")
user User? @relation("UserTickets", fields: [userId], references: [id])
@@map("ticket")
}
model Response {
@@ -128,6 +146,10 @@ model Response {
createdAt DateTime @default(now())
ticket Ticket @relation("TicketResponses", fields: [ticketId], references: [id])
operator User @relation("OperatorResponses", fields: [operatorId], references: [id])
@@map("response")
}
model Notification {
id Int @id @default(autoincrement())
@@ -136,4 +158,6 @@ model Notification {
title String
message String
createdAt DateTime @default(now())
@@map("notification")
}

View File

@@ -43,3 +43,4 @@ export async function createContainer({ vmid, hostname, password, ostemplate, st
throw new Error('Ошибка создания контейнера: ' + (err instanceof Error ? err.message : err));
}
}

View File

@@ -1,8 +1,6 @@
import express from 'express';
import cors from 'cors';
import dotenv from 'dotenv';
import http from 'http';
import { Server as SocketIOServer } from 'socket.io';
import authRoutes from './modules/auth/auth.routes';
import ticketRoutes from './modules/ticket/ticket.routes';
import checkRoutes from './modules/check/check.routes';
@@ -10,25 +8,18 @@ import proxmoxRoutes from '../proxmox/proxmox.routes';
import tariffRoutes from './modules/tariff';
import osRoutes from './modules/os';
import serverRoutes from './modules/server';
import { MonitoringService } from './modules/server/monitoring.service';
dotenv.config();
const app = express();
const server = http.createServer(app);
// Настройка Socket.IO с CORS
const io = new SocketIOServer(server, {
cors: {
origin: ['http://localhost:3000', 'http://localhost:5173'],
methods: ['GET', 'POST'],
credentials: true
}
});
// ИСПРАВЛЕНО: более точная настройка CORS
app.use(cors({
origin: ['http://localhost:3000', 'http://localhost:5173'], // Vite обычно использует 5173
origin: [
'http://localhost:3000',
'http://localhost:5173',
'https://ospab.host'
], // Vite обычно использует 5173
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization']
@@ -78,13 +69,19 @@ app.use('/api/server', serverRoutes);
const PORT = process.env.PORT || 5000;
// Инициализация сервиса мониторинга
const monitoringService = new MonitoringService(io);
monitoringService.startMonitoring();
import { setupConsoleWSS } from './modules/server/server.console';
import https from 'https';
import fs from 'fs';
server.listen(PORT, () => {
console.log(`🚀 Сервер запущен на порту ${PORT}`);
const sslOptions = {
key: fs.readFileSync('/etc/apache2/ssl/ospab.host.key'),
cert: fs.readFileSync('/etc/apache2/ssl/ospab.host.crt'),
};
const httpsServer = https.createServer(sslOptions, app);
setupConsoleWSS(httpsServer);
httpsServer.listen(PORT, () => {
console.log(`🚀 HTTPS сервер запущен на порту ${PORT}`);
console.log(`📊 База данных: ${process.env.DATABASE_URL ? 'подключена' : 'НЕ НАСТРОЕНА'}`);
console.log(`🔌 WebSocket сервер запущен`);
console.log(`📡 Мониторинг серверов активен`);
});

View File

@@ -1,10 +1,14 @@
import { Router } from 'express';
import { PrismaClient } from '@prisma/client';
import { authMiddleware } from '../auth/auth.middleware';
const router = Router();
const prisma = new PrismaClient();
// GET /api/os — получить все ОС
router.use(authMiddleware);
// GET /api/os — получить все ОС (только для авторизованных)
router.get('/', async (req, res) => {
try {
const oses = await prisma.operatingSystem.findMany();

View File

@@ -1,3 +1,19 @@
// Смена root-пароля через SSH (для LXC)
import { exec } from 'child_process';
export async function changeRootPasswordSSH(vmid: number): Promise<{ status: string; password?: string; message?: string }> {
const newPassword = generateSecurePassword();
return new Promise((resolve) => {
exec(`ssh -o StrictHostKeyChecking=no root@${process.env.PROXMOX_NODE} pct set ${vmid} --password ${newPassword}`, (err, stdout, stderr) => {
if (err) {
console.error('Ошибка смены пароля через SSH:', stderr);
resolve({ status: 'error', message: stderr });
} else {
resolve({ status: 'success', password: newPassword });
}
});
});
}
import axios from 'axios';
import crypto from 'crypto';
import dotenv from 'dotenv';

View File

@@ -0,0 +1,70 @@
import { Server as WebSocketServer, WebSocket } from 'ws';
import { Client as SSHClient } from 'ssh2';
import dotenv from 'dotenv';
import { IncomingMessage } from 'http';
import { Server as HttpServer } from 'http';
dotenv.config();
export function setupConsoleWSS(server: HttpServer) {
const wss = new WebSocketServer({ noServer: true });
wss.on('connection', (ws: WebSocket, req: IncomingMessage) => {
const url = req.url || '';
const match = url.match(/\/api\/server\/(\d+)\/console/);
const vmid = match ? match[1] : null;
if (!vmid) {
ws.close();
return;
}
// Получаем IP и root-пароль из БД (упрощённо)
// Здесь можно добавить реальный запрос к Prisma
const host = process.env.PROXMOX_IP || process.env.PROXMOX_NODE;
const username = 'root';
const password = process.env.PROXMOX_ROOT_PASSWORD;
const ssh = new SSHClient();
const port = process.env.PROXMOX_SSH_PORT ? Number(process.env.PROXMOX_SSH_PORT) : 22;
ssh.on('ready', () => {
ssh.shell((err: Error | undefined, stream: any) => {
if (err) {
ws.send('Ошибка запуска shell: ' + err.message);
ws.close();
ssh.end();
return;
}
ws.on('message', (msg: string | Buffer) => {
stream.write(msg.toString());
});
stream.on('data', (data: Buffer) => {
ws.send(data.toString());
});
stream.on('close', () => {
ws.close();
ssh.end();
});
});
}).connect({
host,
port,
username,
password,
hostVerifier: (hash: string) => {
console.log('SSH fingerprint:', hash);
return true; // всегда принимаем fingerprint
}
});
ws.on('close', () => {
ssh.end();
});
});
server.on('upgrade', (request: IncomingMessage, socket: any, head: Buffer) => {
if (request.url?.startsWith('/api/server/') && request.url?.endsWith('/console')) {
wss.handleUpgrade(request, socket, head, (ws: WebSocket) => {
wss.emit('connection', ws, request);
});
}
});
}

View File

@@ -66,13 +66,13 @@ export async function createServer(req: Request, res: Response) {
});
}
// Сохраняем сервер в БД с реальным статусом
// Сохраняем сервер в БД, статус всегда 'running' после покупки
const server = await prisma.server.create({
data: {
userId,
tariffId,
osId,
status: result.containerStatus || 'creating',
status: 'running',
proxmoxId: Number(result.vmid),
ipAddress: result.ipAddress,
rootPassword: result.rootPassword,
@@ -146,10 +146,11 @@ async function handleControl(req: Request, res: Response, action: 'start' | 'sto
const result = await controlContainer(server.proxmoxId, action);
// Polling статуса VM после управления
let newStatus = server.status;
let actionSuccess = false;
let status = '';
let attempts = 0;
const maxAttempts = 10;
if (result.status === 'success') {
let status = '';
let attempts = 0;
const maxAttempts = 10;
while (attempts < maxAttempts) {
await new Promise(resolve => setTimeout(resolve, 3000));
const stats = await getContainerStats(server.proxmoxId);
@@ -158,6 +159,7 @@ async function handleControl(req: Request, res: Response, action: 'start' | 'sto
if ((action === 'start' && status === 'running') ||
(action === 'stop' && status === 'stopped') ||
(action === 'restart' && status === 'running')) {
actionSuccess = true;
break;
}
}
@@ -178,6 +180,11 @@ async function handleControl(req: Request, res: Response, action: 'start' | 'sto
}
await prisma.server.update({ where: { id }, data: { status: newStatus } });
}
// Если статус изменился, считаем действие успешным даже если result.status !== 'success'
if (newStatus !== server.status) {
return res.json({ status: 'success', newStatus, message: 'Статус сервера изменён успешно' });
}
// Если не удалось, возвращаем исходный ответ
res.json({ ...result, status: newStatus });
} catch (error: any) {
res.status(500).json({ error: error?.message || 'Ошибка управления сервером' });
@@ -208,7 +215,9 @@ export async function changeRootPassword(req: Request, res: Response) {
const id = Number(req.params.id);
const server = await prisma.server.findUnique({ where: { id } });
if (!server || !server.proxmoxId) return res.status(404).json({ error: 'Сервер не найден или нет VMID' });
const result = await proxmoxChangeRootPassword(server.proxmoxId);
// Используем SSH для смены пароля
const { changeRootPasswordSSH } = require('./proxmoxApi');
const result = await changeRootPasswordSSH(server.proxmoxId);
if (result?.status === 'success' && result.password) {
await prisma.server.update({ where: { id }, data: { rootPassword: result.password } });
}

View File

@@ -1,10 +1,10 @@
import { Router } from 'express';
import { PrismaClient } from '@prisma/client';
const router = Router();
const prisma = new PrismaClient();
// GET /api/tariff — получить все тарифы
router.get('/', async (req, res) => {
try {
const tariffs = await prisma.tariff.findMany();

View File

@@ -10,7 +10,7 @@
"forceConsistentCasingInFileNames": true,
"moduleResolution": "node16",
"resolveJsonModule": true,
"noEmit": true,
"outDir": "./dist",
"verbatimModuleSyntax": false
},
"include": ["src/**/*.ts"],