176 lines
4.1 KiB
TypeScript
Executable File
176 lines
4.1 KiB
TypeScript
Executable File
import { app, BrowserWindow, ipcMain, dialog } from 'electron';
|
|
import * as path from 'path';
|
|
import * as fs from 'fs';
|
|
import { exec } from 'child_process';
|
|
import { promisify } from 'util';
|
|
|
|
const execAsync = promisify(exec);
|
|
|
|
const CONFIG_DIR = path.join(app.getPath('home'), '.config', 'wireguard-gui');
|
|
const CONFIG_FILE = path.join(CONFIG_DIR, 'current.conf');
|
|
const WG_INTERFACE = 'wg0';
|
|
const WG_CONF_PATH = `/etc/wireguard/${WG_INTERFACE}.conf`;
|
|
|
|
let mainWindow: BrowserWindow | null = null;
|
|
|
|
function createWindow() {
|
|
mainWindow = new BrowserWindow({
|
|
width: 350,
|
|
height: 650,
|
|
resizable: false,
|
|
autoHideMenuBar: true,
|
|
webPreferences: {
|
|
preload: path.join(__dirname, 'preload.js'),
|
|
nodeIntegration: false,
|
|
contextIsolation: true,
|
|
devTools: process.env.NODE_ENV === 'development',
|
|
},
|
|
});
|
|
|
|
// Remove menu bar completely
|
|
mainWindow.setMenuBarVisibility(false);
|
|
|
|
if (process.env.NODE_ENV === 'development') {
|
|
mainWindow.loadURL('http://localhost:5173');
|
|
} else {
|
|
mainWindow.loadFile(path.join(__dirname, '../dist/index.html'));
|
|
}
|
|
}
|
|
|
|
// Ensure config directory exists
|
|
function ensureConfigDir() {
|
|
if (!fs.existsSync(CONFIG_DIR)) {
|
|
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
}
|
|
}
|
|
|
|
// IPC Handlers
|
|
ipcMain.handle('select-config-file', async () => {
|
|
const result = await dialog.showOpenDialog(mainWindow!, {
|
|
properties: ['openFile'],
|
|
filters: [{ name: 'WireGuard Config', extensions: ['conf'] }],
|
|
});
|
|
|
|
if (result.canceled || result.filePaths.length === 0) {
|
|
return { success: false, error: 'No file selected' };
|
|
}
|
|
|
|
return { success: true, filePath: result.filePaths[0] };
|
|
});
|
|
|
|
ipcMain.handle('load-config', async (_event, filePath: string) => {
|
|
try {
|
|
ensureConfigDir();
|
|
|
|
// Copy config to our storage
|
|
fs.copyFileSync(filePath, CONFIG_FILE);
|
|
|
|
// Create symlink with sudo (non-interactive)
|
|
const symlinkCmd = `sudo -n ln -sf ${CONFIG_FILE} ${WG_CONF_PATH}`;
|
|
await execAsync(symlinkCmd);
|
|
|
|
return {
|
|
success: true,
|
|
configName: path.basename(filePath),
|
|
message: 'Config loaded successfully',
|
|
};
|
|
} catch (error: any) {
|
|
return {
|
|
success: false,
|
|
error: error.message || 'Failed to load config',
|
|
};
|
|
}
|
|
});
|
|
|
|
ipcMain.handle('connect-wireguard', async () => {
|
|
try {
|
|
const cmd = `sudo -n wg-quick up ${WG_INTERFACE}`;
|
|
const { stdout, stderr } = await execAsync(cmd);
|
|
|
|
return {
|
|
success: true,
|
|
message: 'Connected successfully',
|
|
output: stdout || stderr,
|
|
};
|
|
} catch (error: any) {
|
|
return {
|
|
success: false,
|
|
error: error.message || 'Failed to connect',
|
|
};
|
|
}
|
|
});
|
|
|
|
ipcMain.handle('disconnect-wireguard', async () => {
|
|
try {
|
|
const cmd = `sudo -n wg-quick down ${WG_INTERFACE}`;
|
|
const { stdout, stderr } = await execAsync(cmd);
|
|
|
|
return {
|
|
success: true,
|
|
message: 'Disconnected successfully',
|
|
output: stdout || stderr,
|
|
};
|
|
} catch (error: any) {
|
|
return {
|
|
success: false,
|
|
error: error.message || 'Failed to disconnect',
|
|
};
|
|
}
|
|
});
|
|
|
|
ipcMain.handle('get-connection-status', async () => {
|
|
try {
|
|
// Check if interface exists without requiring root
|
|
await execAsync(`ip link show ${WG_INTERFACE}`);
|
|
|
|
return {
|
|
success: true,
|
|
connected: true,
|
|
details: `${WG_INTERFACE} is up`,
|
|
};
|
|
} catch {
|
|
// ip link returns error if interface doesn't exist
|
|
return {
|
|
success: true,
|
|
connected: false,
|
|
details: '',
|
|
};
|
|
}
|
|
});
|
|
|
|
ipcMain.handle('get-current-config', async () => {
|
|
try {
|
|
if (fs.existsSync(CONFIG_FILE)) {
|
|
const configName = 'current.conf';
|
|
return {
|
|
success: true,
|
|
hasConfig: true,
|
|
configName,
|
|
};
|
|
}
|
|
return {
|
|
success: true,
|
|
hasConfig: false,
|
|
};
|
|
} catch (error: any) {
|
|
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();
|
|
}
|
|
});
|