@@ -65,6 +65,19 @@ function decryptSshPrivateKey(key) {
6565 return key ;
6666}
6767
68+ function buildSshKeyFilename ( node ) {
69+ const safe = ( value , fallback ) => {
70+ const normalized = String ( value || '' )
71+ . trim ( )
72+ . replace ( / [ ^ a - z A - Z 0 - 9 . _ - ] + / g, '-' )
73+ . replace ( / - + / g, '-' )
74+ . replace ( / ^ - | - $ / g, '' ) ;
75+ return normalized || fallback ;
76+ } ;
77+
78+ return `${ safe ( node . name , 'node' ) } -${ safe ( node . ip , 'unknown' ) } .key` ;
79+ }
80+
6881/**
6982 * Open a direct SSH connection to a node using its stored credentials.
7083 * @returns {Promise<Client> } connected ssh2 Client
@@ -741,6 +754,37 @@ router.post('/nodes/:id/generate-ssh-key', requireAuth, generateSshKeyLimiter, a
741754 }
742755} ) ;
743756
757+ // GET /panel/nodes/:id/download-ssh-key - Download stored SSH private key
758+ router . get ( '/nodes/:id/download-ssh-key' , requireAuth , async ( req , res ) => {
759+ try {
760+ const node = await HyNode . findById ( req . params . id ) . select ( 'name ip ssh.privateKey' ) ;
761+
762+ if ( ! node ) {
763+ return res . status ( 404 ) . type ( 'text/plain; charset=utf-8' ) . send ( 'Node not found' ) ;
764+ }
765+
766+ if ( ! node . ssh ?. privateKey ) {
767+ return res . status ( 404 ) . type ( 'text/plain; charset=utf-8' ) . send ( 'SSH private key not configured' ) ;
768+ }
769+
770+ const privateKey = decryptSshPrivateKey ( node . ssh . privateKey ) ;
771+ const filename = buildSshKeyFilename ( node ) ;
772+
773+ logger . info ( `[Panel] SSH private key downloaded for node ${ node . name } ` ) ;
774+
775+ res . set ( {
776+ 'Content-Type' : 'application/x-pem-file; charset=utf-8' ,
777+ 'Content-Disposition' : `attachment; filename="${ filename } "` ,
778+ 'Cache-Control' : 'no-store' ,
779+ 'X-Content-Type-Options' : 'nosniff' ,
780+ } ) ;
781+ return res . send ( privateKey ) ;
782+ } catch ( error ) {
783+ logger . error ( `[Panel] SSH key download error: ${ error . message } ` ) ;
784+ return res . status ( 500 ) . type ( 'text/plain; charset=utf-8' ) . send ( 'Failed to download SSH private key' ) ;
785+ }
786+ } ) ;
787+
744788// GET /panel/nodes/:id/stats - Получение системной статистики ноды
745789router . get ( '/nodes/:id/stats' , requireAuth , async ( req , res ) => {
746790 try {
@@ -2252,4 +2296,4 @@ router.post('/settings/test-webhook', requireAuth, async (req, res) => {
22522296 }
22532297} ) ;
22542298
2255- module . exports = router ;
2299+ module . exports = router ;
0 commit comments