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