log
This commit is contained in:
@@ -15,14 +15,24 @@ const SERVICE_NAME = 'nebula@jallen-nebula.service';
|
|||||||
const IFACE_NAME = 'jallen-nebula';
|
const IFACE_NAME = 'jallen-nebula';
|
||||||
|
|
||||||
const POLL_INTERVAL_SECS = 5;
|
const POLL_INTERVAL_SECS = 5;
|
||||||
|
const LOG_PREFIX = '[nebula-vpn]';
|
||||||
|
|
||||||
// ── helpers ──────────────────────────────────────────────────────────────────
|
// ── helpers ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
function log(msg) {
|
||||||
|
console.log(`${LOG_PREFIX} ${msg}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function logErr(msg, err) {
|
||||||
|
console.error(`${LOG_PREFIX} ${msg}`, err ?? '');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run a command and return { success, stdout, stderr }.
|
* Run a command and return { success, stdout, stderr }.
|
||||||
* The command is passed as an argv array (no shell expansion).
|
* The command is passed as an argv array (no shell expansion).
|
||||||
*/
|
*/
|
||||||
function runCommand(argv) {
|
function runCommand(argv) {
|
||||||
|
log(`runCommand: ${argv.join(' ')}`);
|
||||||
try {
|
try {
|
||||||
const [ok, stdout, stderr, exitCode] = GLib.spawn_sync(
|
const [ok, stdout, stderr, exitCode] = GLib.spawn_sync(
|
||||||
null, // working dir
|
null, // working dir
|
||||||
@@ -31,21 +41,23 @@ function runCommand(argv) {
|
|||||||
GLib.SpawnFlags.SEARCH_PATH,
|
GLib.SpawnFlags.SEARCH_PATH,
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
return {
|
const result = {
|
||||||
success: ok && exitCode === 0,
|
success: ok && exitCode === 0,
|
||||||
stdout: ok ? new TextDecoder().decode(stdout).trim() : '',
|
stdout: ok ? new TextDecoder().decode(stdout).trim() : '',
|
||||||
stderr: ok ? new TextDecoder().decode(stderr).trim() : '',
|
stderr: ok ? new TextDecoder().decode(stderr).trim() : '',
|
||||||
};
|
};
|
||||||
} catch (_e) {
|
log(`runCommand result: success=${result.success} exitCode=${exitCode} stdout="${result.stdout}" stderr="${result.stderr}"`);
|
||||||
|
return result;
|
||||||
|
} catch (e) {
|
||||||
|
logErr(`runCommand exception for [${argv.join(' ')}]:`, e);
|
||||||
return {success: false, stdout: '', stderr: ''};
|
return {success: false, stdout: '', stderr: ''};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Return true if the systemd service is currently active. */
|
/** Return true if the systemd service is currently active. */
|
||||||
function isServiceActive() {
|
function isServiceActive() {
|
||||||
const r = runCommand([
|
const r = runCommand(['systemctl', 'is-active', '--quiet', SERVICE_NAME]);
|
||||||
'systemctl', 'is-active', '--quiet', SERVICE_NAME,
|
log(`isServiceActive: ${r.success}`);
|
||||||
]);
|
|
||||||
return r.success;
|
return r.success;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,6 +70,7 @@ function getIfaceInfo() {
|
|||||||
// jallen-nebula UP 10.1.1.3/24 fe80::…/64
|
// jallen-nebula UP 10.1.1.3/24 fe80::…/64
|
||||||
const r = runCommand(['ip', '-brief', 'addr', 'show', IFACE_NAME]);
|
const r = runCommand(['ip', '-brief', 'addr', 'show', IFACE_NAME]);
|
||||||
if (!r.success || r.stdout === '') {
|
if (!r.success || r.stdout === '') {
|
||||||
|
log(`getIfaceInfo: interface not found or no output`);
|
||||||
return {found: false, ipv4: null, ipv6: null};
|
return {found: false, ipv4: null, ipv6: null};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,12 +79,14 @@ function getIfaceInfo() {
|
|||||||
const addrs = parts.slice(2);
|
const addrs = parts.slice(2);
|
||||||
const ipv4 = addrs.find(a => /^\d+\.\d+\.\d+\.\d+\//.test(a)) ?? null;
|
const ipv4 = addrs.find(a => /^\d+\.\d+\.\d+\.\d+\//.test(a)) ?? null;
|
||||||
const ipv6 = addrs.find(a => a.includes(':') && !a.startsWith('fe80')) ?? null;
|
const ipv6 = addrs.find(a => a.includes(':') && !a.startsWith('fe80')) ?? null;
|
||||||
|
log(`getIfaceInfo: found=true state=${parts[1]} ipv4=${ipv4} ipv6=${ipv6}`);
|
||||||
return {found: true, ipv4, ipv6};
|
return {found: true, ipv4, ipv6};
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Start or stop the systemd service (requires polkit / passwordless sudo). */
|
/** Start or stop the systemd service (requires polkit / passwordless sudo). */
|
||||||
function setServiceActive(enable) {
|
function setServiceActive(enable) {
|
||||||
const action = enable ? 'start' : 'stop';
|
const action = enable ? 'start' : 'stop';
|
||||||
|
log(`setServiceActive: ${action} ${SERVICE_NAME}`);
|
||||||
runCommand(['systemctl', action, SERVICE_NAME]);
|
runCommand(['systemctl', action, SERVICE_NAME]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,6 +95,7 @@ function setServiceActive(enable) {
|
|||||||
const NebulaIndicator = GObject.registerClass(
|
const NebulaIndicator = GObject.registerClass(
|
||||||
class NebulaIndicator extends PanelMenu.Button {
|
class NebulaIndicator extends PanelMenu.Button {
|
||||||
_init(extensionPath) {
|
_init(extensionPath) {
|
||||||
|
log('NebulaIndicator._init: start');
|
||||||
super._init(0.0, 'Nebula VPN Status');
|
super._init(0.0, 'Nebula VPN Status');
|
||||||
|
|
||||||
this._extensionPath = extensionPath;
|
this._extensionPath = extensionPath;
|
||||||
@@ -127,23 +143,28 @@ class NebulaIndicator extends PanelMenu.Button {
|
|||||||
|
|
||||||
// ── initial state + polling ──
|
// ── initial state + polling ──
|
||||||
this._active = false;
|
this._active = false;
|
||||||
|
log('NebulaIndicator._init: running initial refresh');
|
||||||
this._refresh();
|
this._refresh();
|
||||||
this._timerId = GLib.timeout_add_seconds(
|
this._timerId = GLib.timeout_add_seconds(
|
||||||
GLib.PRIORITY_DEFAULT,
|
GLib.PRIORITY_DEFAULT,
|
||||||
POLL_INTERVAL_SECS,
|
POLL_INTERVAL_SECS,
|
||||||
() => { this._refresh(); return GLib.SOURCE_CONTINUE; },
|
() => { this._refresh(); return GLib.SOURCE_CONTINUE; },
|
||||||
);
|
);
|
||||||
|
log(`NebulaIndicator._init: poll timer registered (id=${this._timerId})`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── private ──────────────────────────────────────────────────────────────
|
// ── private ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
_refresh() {
|
_refresh() {
|
||||||
|
log('_refresh: checking service and interface');
|
||||||
this._active = isServiceActive();
|
this._active = isServiceActive();
|
||||||
const iface = getIfaceInfo();
|
const iface = getIfaceInfo();
|
||||||
|
log(`_refresh: active=${this._active} iface=${JSON.stringify(iface)}`);
|
||||||
this._updateUI(iface);
|
this._updateUI(iface);
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateUI(iface) {
|
_updateUI(iface) {
|
||||||
|
log(`_updateUI: active=${this._active}`);
|
||||||
if (this._active) {
|
if (this._active) {
|
||||||
this._icon.icon_name = 'network-vpn-symbolic';
|
this._icon.icon_name = 'network-vpn-symbolic';
|
||||||
this._label.text = 'VPN';
|
this._label.text = 'VPN';
|
||||||
@@ -166,12 +187,15 @@ class NebulaIndicator extends PanelMenu.Button {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_onToggle() {
|
_onToggle() {
|
||||||
setServiceActive(!this._active);
|
const target = !this._active;
|
||||||
|
log(`_onToggle: toggling to ${target ? 'active' : 'inactive'}`);
|
||||||
|
setServiceActive(target);
|
||||||
// Optimistic update while systemd races
|
// Optimistic update while systemd races
|
||||||
this._active = !this._active;
|
this._active = target;
|
||||||
this._updateUI(getIfaceInfo());
|
this._updateUI(getIfaceInfo());
|
||||||
// Re-poll shortly after to confirm real state
|
// Re-poll shortly after to confirm real state
|
||||||
GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 2, () => {
|
GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 2, () => {
|
||||||
|
log('_onToggle: confirmation poll');
|
||||||
this._refresh();
|
this._refresh();
|
||||||
return GLib.SOURCE_REMOVE;
|
return GLib.SOURCE_REMOVE;
|
||||||
});
|
});
|
||||||
@@ -180,6 +204,7 @@ class NebulaIndicator extends PanelMenu.Button {
|
|||||||
// ── cleanup ───────────────────────────────────────────────────────────────
|
// ── cleanup ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
|
log('NebulaIndicator.destroy');
|
||||||
if (this._timerId) {
|
if (this._timerId) {
|
||||||
GLib.source_remove(this._timerId);
|
GLib.source_remove(this._timerId);
|
||||||
this._timerId = null;
|
this._timerId = null;
|
||||||
@@ -192,11 +217,14 @@ class NebulaIndicator extends PanelMenu.Button {
|
|||||||
|
|
||||||
export default class NebulaVpnExtension extends Extension {
|
export default class NebulaVpnExtension extends Extension {
|
||||||
enable() {
|
enable() {
|
||||||
|
log('enable: creating indicator');
|
||||||
this._indicator = new NebulaIndicator(this.path);
|
this._indicator = new NebulaIndicator(this.path);
|
||||||
Main.panel.addToStatusArea(this.uuid, this._indicator);
|
Main.panel.addToStatusArea(this.uuid, this._indicator);
|
||||||
|
log('enable: indicator added to panel');
|
||||||
}
|
}
|
||||||
|
|
||||||
disable() {
|
disable() {
|
||||||
|
log('disable: destroying indicator');
|
||||||
this._indicator?.destroy();
|
this._indicator?.destroy();
|
||||||
this._indicator = null;
|
this._indicator = null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,6 @@
|
|||||||
"name": "Nebula VPN Status",
|
"name": "Nebula VPN Status",
|
||||||
"description": "Shows the status of the Nebula VPN in the GNOME panel with interface info and a toggle.",
|
"description": "Shows the status of the Nebula VPN in the GNOME panel with interface info and a toggle.",
|
||||||
"uuid": "nebula-vpn-status@mjallen",
|
"uuid": "nebula-vpn-status@mjallen",
|
||||||
"shell-version": ["45", "46", "47", "48"],
|
"shell-version": ["45", "46", "47", "48", "49"],
|
||||||
"version": 1
|
"version": 1
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user