kurulum3
This commit is contained in:
parent
ced2c9d61b
commit
7e222bacbf
462
backend/package-lock.json
generated
462
backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -11,15 +11,19 @@
|
||||
"license": "ISC",
|
||||
"type": "commonjs",
|
||||
"dependencies": {
|
||||
"@prisma/adapter-pg": "^7.9.1",
|
||||
"@prisma/client": "^7.9.1",
|
||||
"axios": "^1.19.0",
|
||||
"cors": "^2.8.6",
|
||||
"dotenv": "^17.4.2",
|
||||
"express": "^5.2.1",
|
||||
"pg": "^8.23.0"
|
||||
"pg": "^8.23.0",
|
||||
"ssh2": "^1.17.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^5.0.6",
|
||||
"@types/node": "^26.2.0",
|
||||
"@types/ssh2": "^1.15.5",
|
||||
"nodemon": "^3.1.14",
|
||||
"prisma": "^7.9.1",
|
||||
"ts-node": "^10.9.2",
|
||||
|
||||
73
backend/simulate.js
Normal file
73
backend/simulate.js
Normal file
@ -0,0 +1,73 @@
|
||||
const { PrismaClient } = require('@prisma/client');
|
||||
const { Pool } = require('pg');
|
||||
const { PrismaPg } = require('@prisma/adapter-pg');
|
||||
const { Client } = require('ssh2');
|
||||
|
||||
const pool = new Pool({ connectionString: 'postgresql://saas_user:saas_secure_pass_2026@10.0.1.151:5432/saas_5651?schema=public' });
|
||||
const adapter = new PrismaPg(pool);
|
||||
const prisma = new PrismaClient({ adapter });
|
||||
|
||||
const host = '10.0.1.150';
|
||||
const password = '02!Aug2013';
|
||||
|
||||
async function generateDeviceVpnConfig(macAddress, vpnIp) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const conn = new Client();
|
||||
const clientName = `device_${macAddress.replace(/:/g, '')}`;
|
||||
|
||||
const setupScript = `
|
||||
#!/bin/bash
|
||||
cd /etc/openvpn/server
|
||||
|
||||
# 1. Create client certificate
|
||||
if [ ! -f "easy-rsa/pki/issued/${clientName}.crt" ]; then
|
||||
cd easy-rsa
|
||||
./easyrsa --batch build-client-full "${clientName}" nopass
|
||||
cd ..
|
||||
fi
|
||||
|
||||
# 2. Assign static IP
|
||||
mkdir -p ccd
|
||||
echo "ifconfig-push ${vpnIp} 255.255.255.0" > "ccd/${clientName}"
|
||||
|
||||
echo "SUCCESS: VPN Config generated for ${clientName} with IP ${vpnIp}"
|
||||
`;
|
||||
|
||||
conn.on('ready', () => {
|
||||
conn.exec(setupScript, (err, stream) => {
|
||||
if (err) return reject(err);
|
||||
stream.on('close', (code) => {
|
||||
conn.end();
|
||||
if (code === 0) resolve(true);
|
||||
else reject(new Error('Script failed with code ' + code));
|
||||
}).on('data', data => console.log('STDOUT: ' + data))
|
||||
.stderr.on('data', data => console.error('STDERR: ' + data));
|
||||
});
|
||||
}).connect({ host, port: 22, username: 'root', password, readyTimeout: 10000 });
|
||||
});
|
||||
}
|
||||
|
||||
async function runTest() {
|
||||
console.log('--- Starting Test: SaaS E2E Tenant & Device Creation ---');
|
||||
|
||||
console.log('1. Creating new Tenant: "Test Hotel A.S."');
|
||||
const tenant = await prisma.tenant.create({ data: { name: 'Test Hotel A.S.' } });
|
||||
|
||||
console.log('2. Allocated VPN IP for new device: 10.10.0.1');
|
||||
const mac = '00:11:22:33:44:55';
|
||||
await prisma.device.create({
|
||||
data: {
|
||||
tenantId: tenant.id,
|
||||
name: 'Test Hotel Gateway',
|
||||
macAddress: mac,
|
||||
vpnIp: '10.10.0.1'
|
||||
}
|
||||
});
|
||||
|
||||
console.log('3. Connecting to VPN Server (10.0.1.150) to generate config...');
|
||||
await generateDeviceVpnConfig(mac, '10.10.0.1');
|
||||
|
||||
console.log('--- Test Completed ---');
|
||||
}
|
||||
|
||||
runTest().catch(console.error).finally(() => prisma.$disconnect());
|
||||
59
backend/simulate.ts
Normal file
59
backend/simulate.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { VpnService } from './src/services/VpnService';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
const vpnService = new VpnService();
|
||||
|
||||
async function runTest() {
|
||||
console.log('--- Starting Test: SaaS E2E Tenant & Device Creation ---');
|
||||
|
||||
// 1. Create a Tenant
|
||||
console.log('1. Creating new Tenant: "Test Hotel A.S."');
|
||||
const tenant = await prisma.tenant.create({
|
||||
data: { name: 'Test Hotel A.S.' }
|
||||
});
|
||||
console.log(` Tenant created with ID: ${tenant.id}`);
|
||||
|
||||
// 2. Figure out VPN IP
|
||||
const baseIp = '10.10.0.';
|
||||
const lastDevice = await prisma.device.findFirst({
|
||||
where: { vpnIp: { startsWith: baseIp } },
|
||||
orderBy: { vpnIp: 'desc' }
|
||||
});
|
||||
|
||||
let nextVpnIp = '10.10.0.1';
|
||||
if (lastDevice && lastDevice.vpnIp) {
|
||||
const lastOctet = parseInt(lastDevice.vpnIp.split('.')[3], 10);
|
||||
nextVpnIp = `${baseIp}${lastOctet + 1}`;
|
||||
}
|
||||
console.log(`2. Allocated VPN IP for new device: ${nextVpnIp}`);
|
||||
|
||||
// 3. Create Device
|
||||
console.log('3. Registering OPNsense device...');
|
||||
const mac = '00:11:22:33:44:55';
|
||||
const device = await prisma.device.create({
|
||||
data: {
|
||||
tenantId: tenant.id,
|
||||
name: 'Test Hotel Gateway',
|
||||
macAddress: mac,
|
||||
vpnIp: nextVpnIp,
|
||||
opnsenseUrl: 'https://192.168.1.1',
|
||||
apiKey: 'testkey',
|
||||
apiSecret: 'testsecret'
|
||||
}
|
||||
});
|
||||
console.log(` Device registered successfully with MAC: ${mac}`);
|
||||
|
||||
// 4. Trigger VPN Generation
|
||||
console.log('4. Connecting to VPN Server (10.0.1.150) via SSH to generate .ovpn config...');
|
||||
try {
|
||||
await vpnService.generateDeviceVpnConfig(mac, nextVpnIp);
|
||||
console.log(' SUCCESS! .ovpn file and ccd config created on VPN server.');
|
||||
} catch (e: any) {
|
||||
console.error(' FAILED to generate VPN config:', e.message);
|
||||
}
|
||||
|
||||
console.log('--- Test Completed ---');
|
||||
}
|
||||
|
||||
runTest().catch(e => console.error(e)).finally(() => prisma.$disconnect());
|
||||
@ -1,8 +1,10 @@
|
||||
import { Router } from 'express';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { VpnService } from '../services/VpnService';
|
||||
|
||||
const router = Router();
|
||||
const prisma = new PrismaClient();
|
||||
const vpnService = new VpnService();
|
||||
|
||||
// Helper to generate a VPN IP for a new device
|
||||
async function getNextVpnIp(): Promise<string> {
|
||||
@ -55,8 +57,14 @@ router.post('/', async (req, res) => {
|
||||
},
|
||||
});
|
||||
|
||||
// TODO: Actually trigger OpenVPN config generation via SSH or scripts to the VPN Server (10.0.1.150)
|
||||
// generateVpnConfig(nextVpnIp, macAddress);
|
||||
// Trigger OpenVPN config generation via SSH to the VPN Server (10.0.1.150)
|
||||
try {
|
||||
await vpnService.generateDeviceVpnConfig(macAddress, nextVpnIp);
|
||||
console.log(`Successfully generated VPN config for ${macAddress} at ${nextVpnIp}`);
|
||||
} catch (vpnErr) {
|
||||
console.error('Failed to generate VPN config:', vpnErr);
|
||||
// We don't fail the API request entirely, but log the error
|
||||
}
|
||||
|
||||
res.status(201).json(newDevice);
|
||||
} catch (error) {
|
||||
|
||||
88
backend/src/services/VpnService.js
Normal file
88
backend/src/services/VpnService.js
Normal file
@ -0,0 +1,88 @@
|
||||
import { Client } from 'ssh2';
|
||||
export class VpnService {
|
||||
host;
|
||||
password;
|
||||
constructor() {
|
||||
this.host = '10.0.1.150'; // VPN Server IP
|
||||
this.password = '02!Aug2013'; // Root password
|
||||
}
|
||||
/**
|
||||
* Automatically generates an OpenVPN client config on the VPN server
|
||||
* and allocates the static VPN IP in the OpenVPN ccd directory.
|
||||
*/
|
||||
async generateDeviceVpnConfig(macAddress, vpnIp) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const conn = new Client();
|
||||
// Clean up the MAC address to use as a client name (remove colons)
|
||||
const clientName = `device_${macAddress.replace(/:/g, '')}`;
|
||||
// The shell script to run on the VPN Server (10.0.1.150)
|
||||
const setupScript = `
|
||||
#!/bin/bash
|
||||
cd /etc/openvpn/server
|
||||
|
||||
# 1. Create client certificate if it doesn't exist
|
||||
if [ ! -f "easy-rsa/pki/issued/\${clientName}.crt" ]; then
|
||||
cd easy-rsa
|
||||
./easyrsa build-client-full "\${clientName}" nopass
|
||||
cd ..
|
||||
fi
|
||||
|
||||
# 2. Assign static IP in ccd (Client Config Directory)
|
||||
mkdir -p ccd
|
||||
echo "ifconfig-push \${vpnIp} 255.255.255.0" > "ccd/\${clientName}"
|
||||
|
||||
# 3. Generate client .ovpn file
|
||||
cat <<EOF > "/root/\${clientName}.ovpn"
|
||||
client
|
||||
dev tun
|
||||
proto tcp
|
||||
remote 10.0.1.150 1194
|
||||
resolv-retry infinite
|
||||
nobind
|
||||
persist-key
|
||||
persist-tun
|
||||
ca ca.crt
|
||||
cert \${clientName}.crt
|
||||
key \${clientName}.key
|
||||
remote-cert-tls server
|
||||
cipher AES-256-CBC
|
||||
verb 3
|
||||
EOF
|
||||
|
||||
echo "SUCCESS: VPN Config generated for \${clientName} with IP \${vpnIp}"
|
||||
`;
|
||||
conn.on('ready', () => {
|
||||
conn.exec(setupScript, (err, stream) => {
|
||||
if (err) {
|
||||
conn.end();
|
||||
return reject(err);
|
||||
}
|
||||
let output = '';
|
||||
stream.on('close', (code, signal) => {
|
||||
conn.end();
|
||||
if (code === 0) {
|
||||
resolve(true);
|
||||
}
|
||||
else {
|
||||
reject(new Error(`VPN Script failed with code ${code}. Output: ${output}`));
|
||||
}
|
||||
}).on('data', (data) => {
|
||||
output += data.toString();
|
||||
console.log('STDOUT: ' + data);
|
||||
}).stderr.on('data', (data) => {
|
||||
output += data.toString();
|
||||
console.error('STDERR: ' + data);
|
||||
});
|
||||
});
|
||||
}).on('error', (err) => {
|
||||
reject(err);
|
||||
}).connect({
|
||||
host: this.host,
|
||||
port: 22,
|
||||
username: 'root',
|
||||
password: this.password,
|
||||
readyTimeout: 10000
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
94
backend/src/services/VpnService.ts
Normal file
94
backend/src/services/VpnService.ts
Normal file
@ -0,0 +1,94 @@
|
||||
import { Client } from 'ssh2';
|
||||
|
||||
export class VpnService {
|
||||
private host: string;
|
||||
private password: string;
|
||||
|
||||
constructor() {
|
||||
this.host = '10.0.1.150'; // VPN Server IP
|
||||
this.password = '02!Aug2013'; // Root password
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically generates an OpenVPN client config on the VPN server
|
||||
* and allocates the static VPN IP in the OpenVPN ccd directory.
|
||||
*/
|
||||
async generateDeviceVpnConfig(macAddress: string, vpnIp: string): Promise<boolean> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const conn = new Client();
|
||||
|
||||
// Clean up the MAC address to use as a client name (remove colons)
|
||||
const clientName = `device_${macAddress.replace(/:/g, '')}`;
|
||||
|
||||
// The shell script to run on the VPN Server (10.0.1.150)
|
||||
const setupScript = `
|
||||
#!/bin/bash
|
||||
cd /etc/openvpn/server
|
||||
|
||||
# 1. Create client certificate if it doesn't exist
|
||||
if [ ! -f "easy-rsa/pki/issued/\${clientName}.crt" ]; then
|
||||
cd easy-rsa
|
||||
./easyrsa build-client-full "\${clientName}" nopass
|
||||
cd ..
|
||||
fi
|
||||
|
||||
# 2. Assign static IP in ccd (Client Config Directory)
|
||||
mkdir -p ccd
|
||||
echo "ifconfig-push \${vpnIp} 255.255.255.0" > "ccd/\${clientName}"
|
||||
|
||||
# 3. Generate client .ovpn file
|
||||
cat <<EOF > "/root/\${clientName}.ovpn"
|
||||
client
|
||||
dev tun
|
||||
proto tcp
|
||||
remote 10.0.1.150 1194
|
||||
resolv-retry infinite
|
||||
nobind
|
||||
persist-key
|
||||
persist-tun
|
||||
ca ca.crt
|
||||
cert \${clientName}.crt
|
||||
key \${clientName}.key
|
||||
remote-cert-tls server
|
||||
cipher AES-256-CBC
|
||||
verb 3
|
||||
EOF
|
||||
|
||||
echo "SUCCESS: VPN Config generated for \${clientName} with IP \${vpnIp}"
|
||||
`;
|
||||
|
||||
conn.on('ready', () => {
|
||||
conn.exec(setupScript, (err, stream) => {
|
||||
if (err) {
|
||||
conn.end();
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
let output = '';
|
||||
stream.on('close', (code: any, signal: any) => {
|
||||
conn.end();
|
||||
if (code === 0) {
|
||||
resolve(true);
|
||||
} else {
|
||||
reject(new Error(`VPN Script failed with code ${code}. Output: ${output}`));
|
||||
}
|
||||
}).on('data', (data: any) => {
|
||||
output += data.toString();
|
||||
console.log('STDOUT: ' + data);
|
||||
}).stderr.on('data', (data: any) => {
|
||||
output += data.toString();
|
||||
console.error('STDERR: ' + data);
|
||||
});
|
||||
});
|
||||
}).on('error', (err) => {
|
||||
reject(err);
|
||||
}).connect({
|
||||
host: this.host,
|
||||
port: 22,
|
||||
username: 'root',
|
||||
password: this.password,
|
||||
readyTimeout: 10000
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
44
backend/tsconfig.json
Normal file
44
backend/tsconfig.json
Normal file
@ -0,0 +1,44 @@
|
||||
{
|
||||
// Visit https://aka.ms/tsconfig to read more about this file
|
||||
"compilerOptions": {
|
||||
// File Layout
|
||||
// "rootDir": "./src",
|
||||
// "outDir": "./dist",
|
||||
|
||||
// Environment Settings
|
||||
// See also https://aka.ms/tsconfig/module
|
||||
"module": "nodenext",
|
||||
"target": "esnext",
|
||||
"types": [],
|
||||
// For nodejs:
|
||||
// "lib": ["esnext"],
|
||||
// "types": ["node"],
|
||||
// and npm install -D @types/node
|
||||
|
||||
// Other Outputs
|
||||
"sourceMap": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
|
||||
// Stricter Typechecking Options
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"exactOptionalPropertyTypes": true,
|
||||
|
||||
// Style Options
|
||||
// "noImplicitReturns": true,
|
||||
// "noImplicitOverride": true,
|
||||
// "noUnusedLocals": true,
|
||||
// "noUnusedParameters": true,
|
||||
// "noFallthroughCasesInSwitch": true,
|
||||
// "noPropertyAccessFromIndexSignature": true,
|
||||
|
||||
// Recommended Options
|
||||
"strict": true,
|
||||
"jsx": "react-jsx",
|
||||
"verbatimModuleSyntax": true,
|
||||
"isolatedModules": true,
|
||||
"noUncheckedSideEffectImports": true,
|
||||
"moduleDetection": "force",
|
||||
"skipLibCheck": true,
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user