initial commit
This commit is contained in:
175
electron/main.ts
Executable file
175
electron/main.ts
Executable file
@@ -0,0 +1,175 @@
|
||||
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();
|
||||
}
|
||||
});
|
||||
10
electron/preload.ts
Executable file
10
electron/preload.ts
Executable file
@@ -0,0 +1,10 @@
|
||||
import { contextBridge, ipcRenderer } from 'electron';
|
||||
|
||||
contextBridge.exposeInMainWorld('electronAPI', {
|
||||
selectConfigFile: () => ipcRenderer.invoke('select-config-file'),
|
||||
loadConfig: (filePath: string) => ipcRenderer.invoke('load-config', filePath),
|
||||
connectWireguard: () => ipcRenderer.invoke('connect-wireguard'),
|
||||
disconnectWireguard: () => ipcRenderer.invoke('disconnect-wireguard'),
|
||||
getConnectionStatus: () => ipcRenderer.invoke('get-connection-status'),
|
||||
getCurrentConfig: () => ipcRenderer.invoke('get-current-config'),
|
||||
});
|
||||
Reference in New Issue
Block a user