packaging netbird with it

This commit is contained in:
2026-06-01 22:29:47 -06:00
committed by Ishan Karmakar
parent 79a161d303
commit c71963fda2
4 changed files with 120 additions and 18 deletions
Executable
BIN
View File
Binary file not shown.
+2 -2
View File
@@ -13,13 +13,13 @@ const targets = [
{ {
label: "macOS arm64", label: "macOS arm64",
assetName: `netbird_${version}_darwin_arm64.tar.gz`, assetName: `netbird_${version}_darwin_arm64.tar.gz`,
outputDir: join(import.meta.dirname, "resources"), outputDir: join(import.meta.dirname, "..", "resources"),
files: [{ source: "netbird", destination: "netbird" }] files: [{ source: "netbird", destination: "netbird" }]
}, },
{ {
label: "Windows amd64", label: "Windows amd64",
assetName: `netbird_${version}_windows_amd64_signed.tar.gz`, assetName: `netbird_${version}_windows_amd64_signed.tar.gz`,
outputDir: join(import.meta.dirname, "resources"), outputDir: join(import.meta.dirname, "..", "resources"),
files: [ files: [
{ source: "netbird.exe", destination: "netbird.exe" }, { source: "netbird.exe", destination: "netbird.exe" },
{ source: "wintun.dll", destination: "wintun.dll" } { source: "wintun.dll", destination: "wintun.dll" }
+20 -5
View File
@@ -55,6 +55,7 @@ app.on('activate', () => {
// In this file you can include the rest of your app's specific main process // In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and import them here. // code. You can also put them in separate files and import them here.
let tray: Tray; let tray: Tray;
let trayMenu: Menu;
let window: BrowserWindow; let window: BrowserWindow;
let pollTimer: NodeJS.Timeout; let pollTimer: NodeJS.Timeout;
let autoConnect = false; let autoConnect = false;
@@ -103,7 +104,8 @@ function updateTray() {
}, },
{ role: 'quit' } { role: 'quit' }
); );
tray.setContextMenu(Menu.buildFromTemplate(template)); trayMenu = Menu.buildFromTemplate(template);
tray.setContextMenu(process.platform === 'darwin' ? null : trayMenu);
} }
function setStatus(status: NetworkStatus) { function setStatus(status: NetworkStatus) {
@@ -141,11 +143,11 @@ async function connect() {
const client = netbirdClient(); const client = netbirdClient();
const serviceReady = await client.ensureService(); const serviceReady = await client.ensureService();
if (!serviceReady) { if (!serviceReady.ok) {
return setStatus({ return setStatus({
state: 'error', state: 'error',
title: 'Connection failed', title: 'Connection failed',
message: `${GATEWAY_HOST} helper did not become ready.` message: serviceReady.message || `${GATEWAY_HOST} helper did not become ready.`
}); });
} }
@@ -225,11 +227,24 @@ function showWindow() {
broadcastStatus(); broadcastStatus();
} }
function toggleWindow() {
if (window.isVisible()) {
window.hide();
return;
}
showWindow();
}
function createTray() { function createTray() {
const icon = nativeImage.createFromPath(resourcePath('VynteIcon.png')).resize({ width: 18, height: 18 }); const icon = nativeImage.createFromPath(resourcePath('VynteIcon.png')).resize({ width: 18, height: 18 });
tray = new Tray(icon); tray = new Tray(icon);
updateTray(); updateTray();
tray.on('click', showWindow); tray.on('click', toggleWindow);
tray.on('right-click', () => {
window.hide();
tray.popUpContextMenu(trayMenu);
});
} }
ipcMain.handle('status:get', () => withAutoConnect(cachedStatus)); ipcMain.handle('status:get', () => withAutoConnect(cachedStatus));
@@ -243,4 +258,4 @@ ipcMain.handle('auto:toggle', () => {
return autoConnect; return autoConnect;
}); });
ipcMain.handle('app:quit', () => app.quit()); ipcMain.handle('app:quit', () => app.quit());
ipcMain.handle('external:openGateway', () => shell.openExternal(MANAGEMENT_URL)); ipcMain.handle('external:openGateway', () => shell.openExternal(MANAGEMENT_URL));
+98 -11
View File
@@ -9,6 +9,20 @@ export interface CommandResult {
error: Error | null; error: Error | null;
} }
export interface ServiceReadyResult {
ok: boolean;
message?: string;
}
function commandDetails(label: string, result: CommandResult) {
const output = `${result.stdout}\n${result.stderr}\n${result.error?.message ?? ''}`.trim();
return output ? `${label}: ${output}` : `${label}: exited with code ${result.code ?? 1}`;
}
function shellQuote(value: string) {
return `'${value.replace(/'/g, "'\\''")}'`;
}
export class NetBirdClient { export class NetBirdClient {
private executablePath: string; private executablePath: string;
@@ -30,8 +44,30 @@ export class NetBirdClient {
}); });
} }
/// Deprecated??? runElevated(args: string[]): Promise<CommandResult> {
runElevated(args: string[]): Promise<Omit<CommandResult, 'code'>> { if (process.platform === 'darwin') {
const command = [this.executablePath, ...args].map(shellQuote).join(' ');
return new Promise(resolve => {
execFile(
'osascript',
['-e', `do shell script ${JSON.stringify(command)} with administrator privileges`],
{
timeout: 180000,
},
(error, stdout, stderr) => {
resolve({
ok: !error,
code: error && typeof (error as NodeJS.ErrnoException).code === 'number' ? (error as NodeJS.ErrnoException).code : 0,
stdout: stdout || '',
stderr: stderr || '',
error
});
}
)
});
}
const quotedExe = this.executablePath.replace(/'/g, "''"); const quotedExe = this.executablePath.replace(/'/g, "''");
const quotedArgs = args.map(arg => `'${String(arg).replace(/'/g, "''")}'`).join(','); const quotedArgs = args.map(arg => `'${String(arg).replace(/'/g, "''")}'`).join(',');
@@ -48,6 +84,7 @@ export class NetBirdClient {
(error, stdout, stderr) => { (error, stdout, stderr) => {
resolve({ resolve({
ok: !error, ok: !error,
code: error && typeof (error as NodeJS.ErrnoException).code === 'number' ? (error as NodeJS.ErrnoException).code : 0,
stdout: stdout || '', stdout: stdout || '',
stderr: stderr || '', stderr: stderr || '',
error error
@@ -63,32 +100,82 @@ export class NetBirdClient {
}); });
} }
async ensureService(): Promise<boolean> { async ensureService(): Promise<ServiceReadyResult> {
const status = await this.status({ timeout: 10000 }); const status = await this.status({ timeout: 10000 });
if (status.ok) return true; if (status.ok) return { ok: true };
await this.run([ const serviceArgs = [
'service',
'install',
'--management-url', '--management-url',
MANAGEMENT_URL, MANAGEMENT_URL,
'--admin-url', '--admin-url',
MANAGEMENT_URL, MANAGEMENT_URL,
'--log-level', '--log-level',
'info' 'info'
];
let install = await this.run([
'service',
'install',
...serviceArgs
]); ]);
await this.run(['service', 'start']); let installDetails = commandDetails('service install', install);
let serviceAlreadyInstalled = installDetails.includes('Init already exists');
if (!install.ok && !serviceAlreadyInstalled && process.platform === 'darwin') {
install = await this.runElevated([
'service',
'install',
...serviceArgs
]);
installDetails = commandDetails('service install', install);
serviceAlreadyInstalled = installDetails.includes('Init already exists');
}
if (!install.ok && !serviceAlreadyInstalled) {
return {
ok: false,
message: installDetails
};
}
let reconfigureDetails = '';
if (serviceAlreadyInstalled) {
const reconfigure = await this.runElevated([
'service',
'reconfigure',
...serviceArgs
]);
if (!reconfigure.ok) {
reconfigureDetails = commandDetails('service reconfigure', reconfigure);
}
}
let start = await this.run(['service', 'start']);
if (!start.ok && process.platform === 'darwin') {
start = await this.runElevated(['service', 'start']);
}
const startDetails = commandDetails('service start', start);
let lastStatus = commandDetails('status', status);
for (let index = 0; index < 20; index++) { for (let index = 0; index < 20; index++) {
const check = await this.status({ timeout: 8000 }); const check = await this.status({ timeout: 8000 });
if (check.ok) return true; if (check.ok) return { ok: true };
lastStatus = commandDetails('status', check);
await new Promise<void>(resolve => setTimeout(resolve, 500)); await new Promise<void>(resolve => setTimeout(resolve, 500));
} }
return false; return {
ok: false,
message: [
serviceAlreadyInstalled ? installDetails : '',
reconfigureDetails,
start.ok ? '' : startDetails,
lastStatus
].filter(Boolean).join('\n\n')
};
} }
connect(): Promise<CommandResult> { connect(): Promise<CommandResult> {
@@ -122,4 +209,4 @@ export class NetBirdClient {
MANAGEMENT_URL MANAGEMENT_URL
], { timeout: 60000 }); ], { timeout: 60000 });
} }
} }