<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptLibraryMappings">
+ <file url="PROJECT" libraries="{@types/dockerode}" />
<includedPredefinedLibrary name="Node.js Core" />
</component>
</project>
\ No newline at end of file
<excludeFolder url="file://$MODULE_DIR$/tmp" />
<excludeFolder url="file://$MODULE_DIR$/.zencoder" />
<excludeFolder url="file://$MODULE_DIR$/bin" />
+ <excludeFolder url="file://$MODULE_DIR$/pm2logs" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="library" name="@types/dockerode" level="application" />
</component>
</module>
\ No newline at end of file
const Routes = require( './src/routes' );
const phsdb = require( './src/phsdb' );
const fs = require('fs');
+const sshConfig = {
+ host:'192.168.1.62', port:22, username:'git', privateKey:fs.readFileSync( '/home/node/.ssh/git-ui' ),
+};
const buffer = require('buffer');
global.Blob = buffer.Blob
});
global.phsdb = phsdb;
+global.fs = fs;
+global.sshConfig = sshConfig;
// noinspection JSCheckFunctionSignatures
app.use( bodyParser.json( {
"cookie-parser": "~1.4.4",
"cors": "^2.8.5",
"debug": "~2.6.9",
+ "dockerode": "^4.0.7",
"express": "5.0.0",
"express-compression": "^1.0.2",
"http-errors": "~1.6.3",
--- /dev/null
+module.exports = {
+ login: (req, res, next) => {
+ console.log('login');
+ },
+};
\ No newline at end of file
--- /dev/null
+const { Client } = require( 'ssh2' );
+
+module.exports = {
+ stopContainer:async ( req, res, next ) => {
+ const { containerId } = req.body;
+ if (!containerId) {
+ return res.status( 400 ).json( { error:'Container ID required' } );
+ }
+
+ try {
+ const container = docker.getContainer( containerId );
+ await container.stop();
+ res.json( { message:`Container ${ containerId } stopped successfully` } );
+ } catch (error) {
+ res.status( 500 ).json( { error:`Failed to stop container: ${ error.message }` } );
+ }
+ }, startContainer:async ( req, res, next ) => {
+ const { containerId } = req.body;
+ if (!containerId) {
+ return res.status( 400 ).json( { error:'Container ID required' } );
+ }
+
+ try {
+ const container = docker.getContainer( containerId );
+ await container.start();
+ res.json( { message:`Container ${ containerId } started successfully` } );
+ } catch (error) {
+ res.status( 500 ).json( { error:`Failed to start container: ${ error.message }` } );
+ }
+ }, getContainers:async ( req, res, next ) => {
+ try {
+ const { all } = req.body;
+ const containers = await docker.listContainers( { all } );
+ res.json( {
+ containers:containers.map( c => ({
+ id:c.Id, name:c.Names[0], state:c.State, status:c.Status,
+ }) ),
+ } );
+ } catch (error) {
+ res.status( 500 ).json( { error:`Failed to list containers: ${ error.message }` } );
+ }
+ }, getImages:async ( req, res, next ) => {
+ try {
+ const images = await docker.listImages();
+ res.json( {
+ images:images.map( i => ({
+ id:i.Id, names:i.RepoTags, tags:i.RepoTags, size:i.Size, created:new Date( i.Created * 1000 ).toISOString(),
+ }) ),
+ } );
+ } catch (error) {
+ res.status( 500 ).json( { error:`Failed to list images: ${ error.message }` } );
+ }
+ }, composeDown:async ( req, res, next ) => {
+ const { composeFile } = req.body;
+ if (!composeFile) {
+ return res.status( 400 ).json( { error:'Compose file path required' } );
+ }
+
+ const conn = new Client();
+ conn.on( 'ready', () => {
+ const command = `docker compose -f ${ composeFile } down`;
+ conn.exec( command, ( err, stream ) => {
+ if (err) {
+ conn.end();
+ return res.status( 500 ).json( { error:'SSH command failed' } );
+ }
+ let output = '';
+ stream.on( 'data', ( data ) => (output += data) );
+ stream.stderr.on( 'data', ( data ) => (output += data) );
+ stream.on( 'close', ( code ) => {
+ conn.end();
+ if (code === 0) {
+ res.json( { message:`Compose services in ${ composeFile } stopped successfully` } );
+ } else {
+ res.status( 500 ).json( { error:`Command failed: ${ output }` } );
+ }
+ } );
+ } );
+ } ).on( 'error', ( err ) => {
+ res.status( 500 ).json( { error:`SSH connection failed: ${ err.message }` } );
+ } ).connect( sshConfig );
+ }
+};
\ No newline at end of file
-const fs = require('fs');
-const { Client } = require('ssh2');
-const sshConfig = {
- host: '192.168.1.62',
- port: 22,
- username: 'git',
- privateKey: fs.readFileSync('/home/node/.ssh/git-ui'),
-};
+const { Client } = require( 'ssh2' );
module.exports = {
- createRepo: (req, res, next) => {
- const { name, type, user } = req.body;
- if (!name.match(/^[a-zA-Z0-9_-]+$/)) {
- return res.status(400).json({ error: 'Invalid repository name' });
- }
- if (type === 'private' && !user) {
- return res.status(400).json({ error: 'Username required for private repository' });
- }
+ createRepo:( req, res, next ) => {
+ const { name, type, user } = req.body;
+ if (!name.match( /^[a-zA-Z0-9_-]+$/ )) {
+ return res.status( 400 ).json( { error:'Invalid repository name' } );
+ }
+ if (type === 'private' && !user) {
+ return res.status( 400 ).json( { error:'Username required for private repository' } );
+ }
+
+ const conn = new Client();
+ conn.on( 'ready', () => {
+ const commands = type === 'private' ? [`mkdir /opt/git/${ name }.git`, `cd /opt/git/${ name }.git`, `git init --bare`, `chown -R ${ user }:git /opt/git/${ name }.git`, `chmod -R u+rwX,go-rwx /opt/git/${ name }.git`,] : [`mkdir /opt/git/${ name }.git`, `cd /opt/git/${ name }.git`, `git init --bare --shared=group`, `chown -R git:git /opt/git/${ name }.git`, `chmod -R g+rwX /opt/git/${ name }.git`, `find /opt/git/${ name }.git -type d -exec chmod g+s {} \\;`, `git config core.sharedRepository group`,];
- const conn = new Client();
- conn.on('ready', () => {
- const commands = type === 'private'
- ? [
- `mkdir /opt/git/${name}.git`,
- `cd /opt/git/${name}.git`,
- `git init --bare`,
- `chown -R ${user}:git /opt/git/${name}.git`,
- `chmod -R u+rwX,go-rwx /opt/git/${name}.git`,
- ]
- : [
- `mkdir /opt/git/${name}.git`,
- `cd /opt/git/${name}.git`,
- `git init --bare --shared=group`,
- `chown -R git:git /opt/git/${name}.git`,
- `chmod -R g+rwX /opt/git/${name}.git`,
- `find /opt/git/${name}.git -type d -exec chmod g+s {} \\;`,
- `git config core.sharedRepository group`,
- ];
+ conn.exec( `sudo -u git bash -c "${ commands.join( ' && ' ) }"`, ( err, stream ) => {
+ if (err) {
+ conn.end();
+ return res.status( 500 ).json( { error:'SSH command failed' } );
+ }
+ let output = '';
+ stream.on( 'data', ( data ) => (output += data) );
+ stream.stderr.on( 'data', ( data ) => (output += data) );
+ stream.on( 'close', ( code ) => {
+ conn.end();
+ if (code === 0) {
+ res.json( { message:`Repository ${ name }.git created successfully` } );
+ } else {
+ res.status( 500 ).json( { error:`Command failed: ${ output }` } );
+ }
+ } );
+ } );
+ } ).on( 'error', ( err ) => {
+ res.status( 500 ).json( { error:`SSH connection failed: ${ err.message }` } );
+ } ).connect( sshConfig );
+ }, getRepos:async ( req, res, next ) => {
+ const conn = new Client();
+ conn.on( 'ready', () => {
+ const command = `ls -1 /opt/git | grep '\.git$' | sed 's/\.git$//'`;
+ conn.exec( command, ( err, stream ) => {
+ if (err) {
+ conn.end();
+ return res.status( 500 ).json( { error:'SSH command failed' } );
+ }
+ let output = '';
+ stream.on( 'data', ( data ) => (output += data) );
+ stream.stderr.on( 'data', ( data ) => (output += data) );
+ stream.on( 'close', ( code ) => {
+ conn.end();
+ if (code === 0) {
+ const repos = output.trim().split( '\n' ).map( name => ({
+ name, cloneUrl:`git@192.168.1.62:/opt/git/${ name }.git`,
+ }) );
+ res.json( { repos } );
+ } else {
+ res.status( 500 ).json( { error:`Command failed: ${ output }` } );
+ }
+ } );
+ } );
+ } ).on( 'error', ( err ) => {
+ res.status( 500 ).json( { error:`SSH connection failed: ${ err.message }` } );
+ } ).connect( sshConfig );
+ }, deleteRepo:async ( req, res, next ) => {
+ const { name } = req.params;
+ if (!name.match( /^[a-zA-Z0-9_-]+$/ )) {
+ return res.status( 400 ).json( { error:'Invalid repository name' } );
+ }
- conn.exec(`sudo -u git bash -c "${commands.join(' && ')}"`, (err, stream) => {
- if (err) {
- conn.end();
- return res.status(500).json({ error: 'SSH command failed' });
+ const conn = new Client();
+ conn.on( 'ready', () => {
+ const command = `rm -rf /opt/git/${ name }.git`;
+ conn.exec( command, ( err, stream ) => {
+ if (err) {
+ conn.end();
+ return res.status( 500 ).json( { error:'SSH command failed' } );
+ }
+ let output = '';
+ stream.on( 'data', ( data ) => (output += data) );
+ stream.stderr.on( 'data', ( data ) => (output += data) );
+ stream.on( 'close', ( code ) => {
+ conn.end();
+ if (code === 0) {
+ res.json( { message:`Repository ${ name } deleted successfully` } );
+ } else {
+ res.status( 500 ).json( { error:`Command failed: ${ output }` } );
}
- let output = '';
- stream.on('data', (data) => (output += data));
- stream.stderr.on('data', (data) => (output += data));
- stream.on('close', (code) => {
- conn.end();
- if (code === 0) {
- res.json({ message: `Repository ${name}.git created successfully` });
- } else {
- res.status(500).json({ error: `Command failed: ${output}` });
- }
- });
- });
- }).on('error', (err) => {
- res.status(500).json({ error: `SSH connection failed: ${err.message}` });
- }).connect(sshConfig);
+ } );
+ } );
+ } ).on( 'error', ( err ) => {
+ res.status( 500 ).json( { error:`SSH connection failed: ${ err.message }` } );
+ } ).connect( sshConfig );
+ }, cloneRepo:async ( req, res, next ) => {
+ const { repoName, deployPath, user } = req.body;
+ if (!repoName || !deployPath || !user) {
+ return res.status( 400 ).json( { error:'Repository name, deployment path, and user are required' } );
+ }
+ if (!repoName.match( /^[a-zA-Z0-9_-]+$/ )) {
+ return res.status( 400 ).json( { error:'Invalid repository name' } );
}
+
+ const conn = new Client();
+ conn.on( 'ready', () => {
+ const commands = [`mkdir -p ${ deployPath }`, `chown ${ user }:${ user } ${ deployPath }`, `su - ${ user } -c "cd ${ deployPath } && git clone git@localhost:/opt/git/${ repoName }.git ."`,];
+
+ conn.exec( commands.join( ' && ' ), ( err, stream ) => {
+ if (err) {
+ conn.end();
+ return res.status( 500 ).json( { error:'SSH command failed' } );
+ }
+ let output = '';
+ stream.on( 'data', ( data ) => (output += data) );
+ stream.stderr.on( 'data', ( data ) => (output += data) );
+ stream.on( 'close', ( code ) => {
+ conn.end();
+ if (code === 0) {
+ res.json( { message:`Repository ${ repoName } cloned to ${ deployPath }` } );
+ } else {
+ res.status( 500 ).json( { error:`Command failed: ${ output }` } );
+ }
+ } );
+ } );
+ } ).on( 'error', ( err ) => {
+ res.status( 500 ).json( { error:`SSH connection failed: ${ err.message }` } );
+ } ).connect( sshConfig );
+ },
};
\ No newline at end of file
--- /dev/null
+const { Client } = require( 'ssh2' );
+
+module.exports = {
+ createClient:( req, res, next ) => {
+ const { clientName, staticIp } = req.body;
+ if (!clientName) {
+ return res.status( 400 ).json( { error:'Client name required' } );
+ }
+ if (staticIp && !staticIp.match( /^10\.8\.0\.\d{1,3}$/ )) {
+ return res.status( 400 ).json( { error:'Invalid static IP (must be 10.8.0.x)' } );
+ }
+
+ const conn = new Client();
+ conn.on( 'ready', () => {
+ const commands = [`cd /etc/openvpn/easy-rsa`, `./easyrsa build-client-full ${ clientName } nopass`, `mkdir -p /etc/openvpn/client-configs`, staticIp ? `echo "ifconfig-push ${ staticIp } 255.255.255.0" > /etc/openvpn/client-configs/${ clientName }.ccd` : 'true', `/usr/share/easy-rsa/pkitool ${ clientName }`, `openssl pkcs12 -export -in pki/issued/${ clientName }.crt -inkey pki/private/${ clientName }.key -certfile pki/ca.crt -out /etc/openvpn/client-configs/${ clientName }.p12 -passout pass:`, `openvpn --genkey --secret /etc/openvpn/client-configs/${ clientName }.tls-auth`, `cat /etc/openvpn/client-template.txt | sed "s/CLIENT_NAME/${ clientName }/" > /etc/openvpn/client-configs/${ clientName }.ovpn`, `echo "<ca>" >> /etc/openvpn/client-configs/${ clientName }.ovpn`, `cat pki/ca.crt >> /etc/openvpn/client-configs/${ clientName }.ovpn`, `echo "</ca>" >> /etc/openvpn/client-configs/${ clientName }.ovpn`, `echo "<cert>" >> /etc/openvpn/client-configs/${ clientName }.ovpn`, `cat pki/issued/${ clientName }.crt >> /etc/openvpn/client-configs/${ clientName }.ovpn`, `echo "</cert>" >> /etc/openvpn/client-configs/${ clientName }.ovpn`, `echo "<key>" >> /etc/openvpn/client-configs/${ clientName }.ovpn`, `cat pki/private/${ clientName }.key >> /etc/openvpn/client-configs/${ clientName }.ovpn`, `echo "</key>" >> /etc/openvpn/client-configs/${ clientName }.ovpn`, `echo "<tls-auth>" >> /etc/openvpn/client-configs/${ clientName }.ovpn`, `cat /etc/openvpn/client-configs/${ clientName }.tls-auth >> /etc/openvpn/client-configs/${ clientName }.ovpn`, `echo "</tls-auth>" >> /etc/openvpn/client-configs/${ clientName }.ovpn`, `cat /etc/openvpn/client-configs/${ clientName }.ovpn`,];
+
+ conn.exec( commands.join( ' && ' ), ( err, stream ) => {
+ if (err) {
+ conn.end();
+ return res.status( 500 ).json( { error:'SSH command failed' } );
+ }
+ let output = '';
+ stream.on( 'data', ( data ) => (output += data) );
+ stream.stderr.on( 'data', ( data ) => (output += data) );
+ stream.on( 'close', ( code ) => {
+ conn.end();
+ if (code === 0) {
+ res.json( {
+ message:`Client ${ clientName } created${ staticIp ? ` with IP ${ staticIp }` : '' }`,
+ ovpn:output
+ } );
+ } else {
+ res.status( 500 ).json( { error:`Command failed: ${ output }` } );
+ }
+ } );
+ } );
+ } ).on( 'error', ( err ) => {
+ res.status( 500 ).json( { error:`SSH connection failed: ${ err.message }` } );
+ } ).connect( sshConfig );
+ }, revokeClient:( req, res, next ) => {
+ const { clientName } = req.body;
+ if (!clientName) {
+ return res.status( 400 ).json( { error:'Client name required' } );
+ }
+
+ const conn = new Client();
+ conn.on( 'ready', () => {
+ const commands = [`cd /etc/openvpn/easy-rsa`, `./easyrsa revoke ${ clientName }`, `./easyrsa gen-crl`, `cp pki/crl.pem /etc/openvpn/crl.pem`, `rm -f /etc/openvpn/client-configs/${ clientName }.ccd`, `systemctl restart openvpn@server`,];
+
+ conn.exec( commands.join( ' && ' ), ( err, stream ) => {
+ if (err) {
+ conn.end();
+ return res.status( 500 ).json( { error:'SSH command failed' } );
+ }
+ let output = '';
+ stream.on( 'data', ( data ) => (output += data) );
+ stream.stderr.on( 'data', ( data ) => (output += data) );
+ stream.on( 'close', ( code ) => {
+ conn.end();
+ if (code === 0) {
+ res.json( { message:`Client ${ clientName } revoked` } );
+ } else {
+ res.status( 500 ).json( { error:`Command failed: ${ output }` } );
+ }
+ } );
+ } );
+ } ).on( 'error', ( err ) => {
+ res.status( 500 ).json( { error:`SSH connection failed: ${ err.message }` } );
+ } ).connect( sshConfig );
+ }, getClients:async ( req, res, next ) => {
+ const conn = new Client();
+ conn.on( 'ready', () => {
+ const command = `cat /etc/openvpn/server/status`;
+ conn.exec( command, ( err, stream ) => {
+ if (err) {
+ conn.end();
+ return res.status( 500 ).json( { error:'SSH command failed' } );
+ }
+ let output = '';
+ stream.on( 'data', ( data ) => (output += data) );
+ stream.stderr.on( 'data', ( data ) => (output += data) );
+ stream.on( 'close', ( code ) => {
+ conn.end();
+ if (code === 0) {
+ const clients = output.split( '\n' ).filter( line => line.includes( 'CLIENT_LIST' ) ).map( line => {
+ const parts = line.split( ',' );
+ return { name:parts[1], ip:parts[2], connectedSince:parts[8] };
+ } );
+ res.json( { clients } );
+ } else {
+ res.status( 500 ).json( { error:`Command failed: ${ output }` } );
+ }
+ } );
+ } );
+ } ).on( 'error', ( err ) => {
+ res.status( 500 ).json( { error:`SSH connection failed: ${ err.message }` } );
+ } ).connect( sshConfig );
+ }
+};
\ No newline at end of file
--- /dev/null
+const express = require('express');
+const router = express.Router();
+const { validateAuth } = require('../middleware/routeHelpers');
+const authController = require('../controllers/auth.controller');
+
+module.exports = (passport) => {
+ router.post('/login', authController.login);
+ return router;
+};
\ No newline at end of file
--- /dev/null
+const express = require( 'express' );
+const router = express.Router();
+const { validateAuth } = require( '../middleware/routeHelpers' );
+const dockerController = require( '../controllers/docker.controller' );
+
+module.exports = ( passport ) => {
+ router.post( '/start-container', dockerController.startContainer );
+ router.post( '/stop-container', dockerController.stopContainer );
+ router.get( '/images', dockerController.getImages );
+ router.get( '/containers', dockerController.getContainers );
+ router.post( '/compose-down', dockerController.composeDown );
+ return router;
+};
\ No newline at end of file
-const express = require('express');
+const express = require( 'express' );
const router = express.Router();
-const { validateAuth } = require('../middleware/routeHelpers');
-const gitController = require('../controllers/git.controller');
+const { validateAuth } = require( '../middleware/routeHelpers' );
+const gitController = require( '../controllers/git.controller' );
-module.exports = (passport) => {
+module.exports = ( passport ) => {
// router.use( validateAuth( passport ) );
- router.post('/create-repo', gitController.createRepo);
+ router.post( '/create-repo', gitController.createRepo );
+ router.get( '/', gitController.getRepos );
+ router.delete( '/:name', gitController.deleteRepo );
+ router.get( '/clone-repo', gitController.cloneRepo );
return router;
};
\ No newline at end of file
-const express = require('express');
+const express = require( 'express' );
const router = express.Router();
module.exports.APIRoutes = ( passport ) => {
- /* GET home page. */
- router.get('/', function(req, res, next) {
- res.render('index', { title: 'Express' });
- });
- router.use( '/git', require('./git.routes')(passport) );
+ router.use( '/git', require( './git.routes' )( passport ) );
+ router.use( '/docker', require( './docker.routes' )( passport ) );
+ router.use( '/vpn', require( './vpn.routes' )( passport ) );
return router;
-}
+};
--- /dev/null
+const express = require( 'express' );
+const router = express.Router();
+const { validateAuth } = require( '../middleware/routeHelpers' );
+const vpnController = require( '../controllers/vpn.controller' );
+
+module.exports = ( passport ) => {
+ router.post( '/create-client', validateAuth( passport ), vpnController.createClient );
+ router.delete( '/revoke-client/', validateAuth( passport ), vpnController.revokeClient );
+ router.get( '/clients', validateAuth( passport ), vpnController.getClients );
+ return router;
+};
\ No newline at end of file