polaris-vpn 1.1.0 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.1.2] - 2026-07-22
9
+
10
+ ### Fixed
11
+
12
+ - **Start Command**: Fixed `ReferenceError: config is not defined` crash when invoking `polaris start` without `--server` argument.
13
+ - **WireGuard & AmneziaWG Status**: Fixed `polaris status` and `polaris check` reporting false proxy failures for system-wide VPN interfaces.
14
+ - **AmneziaWG Bandwidth Monitoring**: Updated `polaris monitor`, `dashboard`, `status`, and TUI components to query `awg` interface statistics and accept `--mode amneziawg`.
15
+ - **Deploy Local Auto-Connect**: Updated `polaris deploy` to automatically trigger local connection passing correct server and mode arguments upon provisioning completion.
16
+ - **Status Table Formatting**: Replaced invalid SOCKS5 proxy port display (`socks5://127.0.0.1:0`) with clear `System-wide (All OS traffic)` label for WireGuard and AmneziaWG connections.
17
+
8
18
  ## [1.0.0] - 2026-07-15
9
19
 
10
20
  ### Added
package/README.md CHANGED
@@ -1,132 +1,109 @@
1
- # polaris — Leave no trace
1
+ # Polaris VPN
2
2
 
3
- ![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)
3
+ ![Polaris VPN Terminal Demo](./docs/demo.gif)
4
4
 
5
- <div align="center">
6
- <img src="./docs/demo.gif" alt="Polaris VPN Terminal Demo" width="800">
7
- </div>
5
+ **Command your privacy.**
8
6
 
9
- Your True North in Digital Privacy. `polaris` is a production-quality, open-source, self-hosted VPN CLI tool. It supports SSH dynamic port forwarding, WireGuard, and AmneziaWG (Stealth) tunnels.
7
+ [![NPM Version](https://img.shields.io/npm/v/polaris-vpn?color=88c0d0&label=npm&style=flat-square)](https://www.npmjs.com/package/polaris-vpn)
8
+ [![License](https://img.shields.io/badge/license-Apache%202.0-5e81ac?style=flat-square)](https://github.com/Divyo/polaris-vpn)
9
+ [![Node version](https://img.shields.io/badge/node-%3E%3D%2018.0.0-a3be8c?style=flat-square)](https://nodejs.org)
10
+ ![Platform](https://img.shields.io/badge/platform-macOS%20%7C%20Linux%20%7C%20Windows-ebcb8b?style=flat-square)
10
11
 
11
- ### Prerequisites
12
+ A production-grade, self-hosted VPN CLI with a beautiful Terminal User
13
+ Interface (TUI).
12
14
 
13
- - Node.js (v18+)
14
- - SSH access to a remote Linux VPS (Ubuntu/Debian recommended)
15
- - **Linux/macOS**: `sudo` privileges for configuring WireGuard and network routes locally.
16
- - **Windows**: The official [WireGuard for Windows](https://www.wireguard.com/install/) client must be installed, and `polaris` must be run from an Administrator prompt.
15
+ Deploy an enterprise-grade WireGuard or Stealth VPN in under 60 seconds.
17
16
 
18
- We highly recommend **Oracle Cloud Free Tier** for your VPS.
17
+ ---
19
18
 
20
- ## Install
19
+ ## ✨ Features
21
20
 
22
- ```bash
23
- npm install -g polaris-vpn
24
- ```
21
+ - **🚀 Automated Deployment**: Instantly provision a WireGuard or AmneziaWG VPN
22
+ on any fresh Linux VPS.
23
+ - **🎨 Beautiful TUI**: A master dashboard with real-time bandwidth
24
+ monitoring, ping latency, and server locations.
25
+ - **🛡️ Smart Kill-Switch**: Native OS firewall integration (`pf` for Mac,
26
+ `iptables` for Linux) that ensures 0 leaks.
27
+ - **👻 Stealth Mode**: DPI (Deep Packet Inspection) bypass using AmneziaWG to
28
+ defeat state-level censorship.
29
+ - **🔗 SSH Fallback**: Can't install WireGuard? Polaris seamlessly falls back
30
+ to an encrypted SOCKS5 SSH tunnel.
31
+ - **☁️ Own Your Infra**: We run no servers. You bring your own VPS (Oracle
32
+ Free Tier recommended).
25
33
 
26
- ## Quickstart (30 seconds)
27
-
28
- Once you have your server's IP address and SSH credentials (e.g. `ubuntu@1.2.3.4`), just run:
34
+ ## 📦 Installation
29
35
 
30
36
  ```bash
31
- polaris start --server ubuntu@1.2.3.4
37
+ npm install -g polaris-vpn
32
38
  ```
33
39
 
34
- This will instantly create an encrypted SOCKS5 proxy on `127.0.0.1:1080` and route your traffic through it.
35
-
36
- Verify it's working:
40
+ Requires Node.js 18+
37
41
 
38
- ```bash
39
- polaris check
40
- ```
42
+ ## ⚡ Quick Start
41
43
 
42
- When you're done:
44
+ Got a fresh Linux VPS? Let Polaris set everything up for you.
43
45
 
44
46
  ```bash
45
- polaris stop
46
- ```
47
-
48
- ## Command Reference
49
-
50
- | Command | Description |
51
- | --- | --- |
52
- | `polaris deploy --server user@host --mode amneziawg` | Provision server with AmneziaWG (Stealth Mode) |
53
- | `polaris start --server user@host [--port 1080] [--mode <type>]` | Start the encrypted tunnel. |
54
- | `polaris stop` | Stop the active tunnel. |
55
- | `polaris dashboard` | Opens a rich Terminal User Interface (TUI) with real-time stats. |
56
- | `polaris status [--full]` | Show current tunnel status. `--full` shows GeoIP, ping latency, and WG stats. |
57
- | `polaris monitor` | Live bandwidth monitor for WireGuard tunnels. |
58
- | `polaris killswitch on/off` | Enable or disable the system-wide VPN kill switch. |
59
- | `polaris peer add <name>` | Generate and add a new peer config for WireGuard/AmneziaWG. |
60
- | `polaris peer list` | List all peers configured on the server. |
61
- | `polaris peer remove <name>` | Revoke a peer from the server. |
62
- | `polaris peer qr <name>` | Display the peer config as a QR code for mobile devices. |
63
- | `polaris check` | Run a 3-point privacy check (IP, DNS leak, IPv6 leak). |
64
- | `polaris add <alias> --server user@host` | Save a server profile for quick access. |
65
- | `polaris list` | List all saved server profiles. |
66
- | `polaris use <alias>` | Set a saved profile as the active default. |
67
- | `polaris server start` | Start a local REST API on `127.0.0.1:7070`. |
68
- | `polaris update` | Update `polaris-vpn` to the latest version via npm. |
47
+ # 1. Provision the server automatically
48
+ polaris deploy --server root@1.2.3.4 --mode amneziawg
69
49
 
70
- #### `polaris dashboard`
71
-
72
- Opens a rich Terminal User Interface (TUI) with real-time graphs, server location maps, and live statistics (Rx/Tx, Ping).
73
-
74
- #### `polaris status [--full]`
75
-
76
- Displays the connection state, mode, current proxy IP, and active server. Use `--full` to query GeoIP and ping latency.
50
+ # 2. Connect to your new VPN
51
+ polaris start --server root@1.2.3.4
52
+ ```
77
53
 
78
- #### `polaris monitor`
54
+ > **Pro Tip**: Run `polaris add my-server --server root@1.2.3.4`
55
+ > so you can just type `polaris start` next time!
79
56
 
80
- Live bandwidth monitor that actively graphs the download and upload speeds of your tunnel.
57
+ ## 🖥️ The Dashboard
81
58
 
82
- #### `polaris check`
59
+ Experience the modern terminal UI by running:
83
60
 
84
- Checks if your IP matches the proxy, tests for DNS leaks via `ipleak.net`, and verifies IPv6 routing.
61
+ ```bash
62
+ polaris
63
+ ```
85
64
 
86
- > All commands support the `--json` flag for machine-readable output.
65
+ Use your **Arrow Keys** to navigate through:
87
66
 
88
- ## Oracle Cloud Free Tier Setup
67
+ - **Home**: Quick connect/disconnect and privacy check tools.
68
+ - **Live Monitor**: Watch your real-time Rx/Tx bandwidth graphs and server latency.
69
+ - **Peers**: Add or revoke VPN access for friends, or generate a QR code for
70
+ your phone.
71
+ - **Settings**: Manage your saved server profiles.
89
72
 
90
- The recommended free server is Oracle Cloud Always Free tier:
73
+ ## 🛠️ CLI Command Reference
91
74
 
92
- - **ARM VM (A1.Flex)**: 2 OCPUs, 12 GB RAM — completely free forever.
93
- - 10 TB outbound bandwidth/month — more than enough for personal use.
94
- - Best regions from South/Southeast Asia: India (Hyderabad) ~30ms, Singapore ~50ms, Japan (Tokyo) ~80ms.
95
- - Sign up at [cloud.oracle.com](https://cloud.oracle.com) (requires a credit card for verification but charges nothing for Always Free resources).
96
- - After VM creation, just grab the IP and SSH key, and run `polaris start --server ubuntu@<your-oracle-ip>`.
75
+ Prefer the raw CLI? Everything is accessible via commands:
97
76
 
98
- ## Shared Server Model
77
+ | Command | Description |
78
+ | :--- | :--- |
79
+ | `polaris deploy` | Provision a server with WireGuard or AmneziaWG |
80
+ | `polaris start` | Connect to the VPN and engage the Kill-Switch |
81
+ | `polaris stop` | Disconnect and restore normal internet routing |
82
+ | `polaris dashboard` | Open the TUI Live Monitor directly |
83
+ | `polaris status --full` | Check connection state, GeoIP, and latency |
84
+ | `polaris peer add <name>` | Generate a new peer config for a friend/device |
85
+ | `polaris peer qr <name>` | Display the config as a QR code for mobile apps |
86
+ | `polaris check` | Run a 3-point privacy test (IP, DNS leak, IPv6 leak) |
99
87
 
100
- One Oracle VM can serve multiple people:
88
+ *(All commands support the `--json` flag for machine-readable output or scripting)*
101
89
 
102
- - Light browsing (1–2 Mbps/user): 25–50 people comfortably.
103
- - YouTube/social (5 Mbps/user): 8–10 people simultaneously.
104
- - One person owns the VM, others get SSH keys.
105
- - Or each person creates their own free Oracle account for full isolation.
90
+ ## ☁️ The Oracle Cloud Advantage
106
91
 
107
- ## How It Works
92
+ We highly recommend using the **Oracle Cloud Always Free Tier** to
93
+ host your Polaris server:
108
94
 
109
- `polaris` uses standard SSH dynamic port forwarding (`-D` flag) behind the scenes. It sets up a secure, encrypted tunnel from your local machine to your server, and exposes a local SOCKS5 proxy. This gives you instant VPN-like privacy without installing *any* server-side software.
95
+ - **ARM VM (A1.Flex)**: Up to 4 OCPUs and 24 GB RAM completely free forever.
96
+ - **10 TB Bandwidth**: More than enough for high-speed streaming and gaming.
97
+ - No third-party VPN company logging your data or selling your traffic.
98
+ You are in total command.
110
99
 
111
- ## Trust Model
100
+ ## 🔒 Trust Model
112
101
 
113
- - **You own the server**: No reliance on third-party VPN providers.
102
+ - **You own the server**: No reliance on third-party commercial VPN providers.
114
103
  - **Zero telemetry**: No tracking, no crash reporting, no analytics.
115
- - **No central infra**: We run nothing. You bring your own VPS.
116
- - **Open Source**: Apache 2.0 licensed, fully auditable.
117
-
118
- ## Roadmap
119
-
120
- - **v0.1**: MVP — SSH SOCKS5 tunnel
121
- - **v0.5**: WireGuard full tunnel + server provisioning
122
- - **v0.6**: Windows NT Service bindings + Blessed TUI Dashboard
123
- - **v0.7**: AmneziaWG stealth mode (DPI bypass)
124
- - **v1.0**: Multi-peer management + QR codes + Kill switch (Current)
125
-
126
- ## Contributing
127
-
128
- Contributions, issues, and feature requests are welcome!
129
-
130
- ## License
104
+ - **Local Kill-Switch**: Traffic is strictly locked to the VPN interface to
105
+ prevent IP leaks.
106
+ - **Open Source**: Apache 2.0 licensed, fully auditable codebase.
131
107
 
132
- Apache 2.0
108
+ ---
109
+ *Built for developers who value privacy, speed, and beautiful terminal tools.*
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polaris-vpn",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "description": "Your True North in Digital Privacy. A self-hosted VPN CLI.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -74,5 +74,10 @@
74
74
  "socks-proxy-agent": "^10.1.0",
75
75
  "ssh2": "^1.17.0",
76
76
  "update-notifier": "^7.3.1"
77
+ },
78
+ "overrides": {
79
+ "lodash": "^4.18.1",
80
+ "uuid": "^11.1.1",
81
+ "xml2js": "^0.6.2"
77
82
  }
78
83
  }
@@ -22,12 +22,13 @@ export default async (options) => {
22
22
  if (spinner) spinner.text = 'Checking IP address...';
23
23
  if (info) {
24
24
  try {
25
- const tunnelIp = await getProxiedIp(info.port);
25
+ const isSystemWide = info.mode === 'wireguard' || info.mode === 'amneziawg';
26
+ const tunnelIp = isSystemWide ? await getPublicIp() : await getProxiedIp(info.port);
26
27
  results.ip = true;
27
28
  details.ip = `Tunnel active (${(info.mode || 'ssh').toUpperCase()}): ${tunnelIp}`;
28
29
  } catch (e) {
29
30
  results.ip = false;
30
- details.ip = `Tunnel process running but proxy failed: ${e.message}`;
31
+ details.ip = `Tunnel active but IP check failed: ${e.message}`;
31
32
  }
32
33
  } else {
33
34
  const pubIp = await getPublicIp();
@@ -1,7 +1,7 @@
1
1
  import blessed from 'blessed';
2
2
  import contrib from 'blessed-contrib';
3
3
  import { getActiveTunnel } from '../core/tunnel-service.js';
4
- import { getProxiedIp } from '../net/ip-check.js';
4
+ import { getProxiedIp, getPublicIp } from '../net/ip-check.js';
5
5
  import fetch from 'node-fetch';
6
6
  import { spawnSync } from 'child_process';
7
7
  import os from 'os';
@@ -67,8 +67,12 @@ const formatBytes = (bytes) => {
67
67
  return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
68
68
  };
69
69
 
70
- const getWgStats = () => {
71
- const dumpRes = spawnSync('sudo', ['wg', 'show', 'all', 'dump'], { encoding: 'utf-8' });
70
+ const getWgStats = (isAwg = false) => {
71
+ const cmd = isAwg ? 'awg' : 'wg';
72
+ let dumpRes = spawnSync('sudo', [cmd, 'show', 'all', 'dump'], { encoding: 'utf-8' });
73
+ if (dumpRes.status !== 0 && isAwg) {
74
+ dumpRes = spawnSync('sudo', ['wg', 'show', 'all', 'dump'], { encoding: 'utf-8' });
75
+ }
72
76
  if (dumpRes.status !== 0) return null;
73
77
  const lines = dumpRes.stdout.trim().split('\n');
74
78
  if (lines.length <= 1) return null;
@@ -201,7 +205,8 @@ export default async () => {
201
205
  if (!geoFetched) {
202
206
  geoFetched = true;
203
207
  try {
204
- currentIp = await getProxiedIp(info.port);
208
+ const isSystemWide = info.mode === 'wireguard' || info.mode === 'amneziawg';
209
+ currentIp = isSystemWide ? await getPublicIp() : await getProxiedIp(info.port);
205
210
  geoData = await getGeoIp(currentIp);
206
211
  if (geoData) {
207
212
  mapWidget.addMarker({ lat: geoData.lat, lon: geoData.lon, color: 'red', char: 'X' });
@@ -227,7 +232,7 @@ export default async () => {
227
232
 
228
233
  // 4. Update WG Stats & Chart
229
234
  if (info.mode === 'wireguard' || info.mode === 'amneziawg' || info.mode === 'auto') {
230
- const stats = getWgStats();
235
+ const stats = getWgStats(info.mode === 'amneziawg');
231
236
  if (stats) {
232
237
  if (!hasInitWg) {
233
238
  prevRx = stats.totalRx;
@@ -36,7 +36,8 @@ export default async (options) => {
36
36
  }
37
37
 
38
38
  // Automatically trigger local connection
39
- await startCommand({ mode: 'wireguard', json: isJson });
39
+ const mode = options.mode || 'wireguard';
40
+ await startCommand({ server, mode, json: isJson });
40
41
 
41
42
  } catch (err) {
42
43
  if (spinner) spinner.fail('Deployment failed');
@@ -49,7 +49,7 @@ export default async (options) => {
49
49
  const isJson = options.json;
50
50
  const info = getActiveTunnel();
51
51
 
52
- if (!info || info.mode !== 'wireguard') {
52
+ if (!info || (info.mode !== 'wireguard' && info.mode !== 'amneziawg')) {
53
53
  if (isJson) {
54
54
  console.log(JSON.stringify({ error: 'WireGuard tunnel is not active' }));
55
55
  } else {
@@ -51,8 +51,7 @@ export default async (options) => {
51
51
  const requestedMode = options.mode || 'auto';
52
52
 
53
53
  if (!server) {
54
- const active = config.get('activeProfile');
55
- const profiles = config.get('profiles') || {};
54
+ const { profiles, active } = getProfiles();
56
55
 
57
56
  if (active && profiles[active]) {
58
57
  server = profiles[active];
@@ -1,5 +1,5 @@
1
1
  import { getActiveTunnel } from '../core/tunnel-service.js';
2
- import { getProxiedIp } from '../net/ip-check.js';
2
+ import { getProxiedIp, getPublicIp } from '../net/ip-check.js';
3
3
  import { createTable, createSpinner, printError } from '../utils/display.js';
4
4
  import chalk from 'chalk';
5
5
  import fetch from 'node-fetch';
@@ -41,8 +41,12 @@ const formatBytes = (bytes) => {
41
41
  return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
42
42
  };
43
43
 
44
- const getWgStats = () => {
45
- const dumpRes = spawnSync('sudo', ['wg', 'show', 'all', 'dump'], { encoding: 'utf-8' });
44
+ const getWgStats = (isAwg = false) => {
45
+ const cmd = isAwg ? 'awg' : 'wg';
46
+ let dumpRes = spawnSync('sudo', [cmd, 'show', 'all', 'dump'], { encoding: 'utf-8' });
47
+ if (dumpRes.status !== 0 && isAwg) {
48
+ dumpRes = spawnSync('sudo', ['wg', 'show', 'all', 'dump'], { encoding: 'utf-8' });
49
+ }
46
50
  if (dumpRes.status !== 0) return null;
47
51
  const lines = dumpRes.stdout.trim().split('\n');
48
52
  if (lines.length <= 1) return null;
@@ -72,10 +76,11 @@ export default async (options) => {
72
76
  }
73
77
 
74
78
  let currentIp = 'Unknown';
75
- const spinner = isJson ? null : createSpinner('Checking proxy status...').start();
79
+ const isSystemWide = info.mode === 'wireguard' || info.mode === 'amneziawg';
80
+ const spinner = isJson ? null : createSpinner(isSystemWide ? 'Checking public IP...' : 'Checking proxy status...').start();
76
81
 
77
82
  try {
78
- currentIp = await getProxiedIp(info.port);
83
+ currentIp = isSystemWide ? await getPublicIp() : await getProxiedIp(info.port);
79
84
  if (spinner) spinner.stop();
80
85
 
81
86
  let geo = 'N/A';
@@ -92,8 +97,8 @@ export default async (options) => {
92
97
  latency = pingServer(serverIp);
93
98
  geo = await getGeoIp(currentIp);
94
99
 
95
- if (info.mode === 'wireguard') {
96
- const stats = getWgStats();
100
+ if (isSystemWide) {
101
+ const stats = getWgStats(info.mode === 'amneziawg');
97
102
  if (stats) {
98
103
  dataUsage = `${formatBytes(stats.totalRx)} / ${formatBytes(stats.totalTx)}`;
99
104
  }
@@ -128,8 +133,16 @@ export default async (options) => {
128
133
  table.push(
129
134
  ['Status', chalk.green('Connected')],
130
135
  ['Mode', (info.mode || 'ssh').toUpperCase()],
131
- ['Server', info.server],
132
- ['Proxy', `socks5://127.0.0.1:${info.port}`],
136
+ ['Server', info.server]
137
+ );
138
+
139
+ if (isSystemWide) {
140
+ table.push(['Connection', chalk.cyan('System-wide (All OS traffic)')]);
141
+ } else {
142
+ table.push(['Proxy', `socks5://127.0.0.1:${info.port}`]);
143
+ }
144
+
145
+ table.push(
133
146
  ['Current IP', chalk.cyan(currentIp)],
134
147
  ['Uptime', `${uptimeMin} minutes`],
135
148
  ['PID', info.pid.toString()]
@@ -87,8 +87,12 @@ const fmtBytes = (n) => {
87
87
  return `${(n / Math.pow(k, i)).toFixed(1)} ${s[i]}`;
88
88
  };
89
89
 
90
- const wgStats = () => {
91
- const r = spawnSync('sudo', ['wg', 'show', 'all', 'dump'], { encoding: 'utf-8' });
90
+ const wgStats = (isAwg = false) => {
91
+ const cmd = isAwg ? 'awg' : 'wg';
92
+ let r = spawnSync('sudo', [cmd, 'show', 'all', 'dump'], { encoding: 'utf-8' });
93
+ if (r.status !== 0 && isAwg) {
94
+ r = spawnSync('sudo', ['wg', 'show', 'all', 'dump'], { encoding: 'utf-8' });
95
+ }
92
96
  if (r.status !== 0) return null;
93
97
  const lines = r.stdout.trim().split('\n');
94
98
  if (lines.length <= 1) return null;
@@ -293,7 +297,7 @@ export default async () => {
293
297
  if (info) {
294
298
  const upMin = Math.floor((Date.now() - new Date(info.startTime).getTime()) / 60000);
295
299
  const ping = pingServer(info.server.split('@').pop());
296
- const wg = wgStats();
300
+ const wg = wgStats(info.mode === 'amneziawg');
297
301
  L.push(`${t(D.accent, b('◈ Tunnel Status'))} ${t(D.success, '⬤ ACTIVE')}`);
298
302
  L.push(hr()); L.push('');
299
303
  L.push(` ${mu('Server ')} ${b(info.server)}`);
@@ -391,7 +395,13 @@ export default async () => {
391
395
  const renderPeers = () => {
392
396
  const L = [];
393
397
  L.push(`${t(D.accent, b('≡ WireGuard Peers'))}`); L.push(hr()); L.push('');
394
- const r = spawnSync('sudo', ['wg', 'show', 'all', 'dump'], { encoding: 'utf-8' });
398
+ const info = getActiveTunnel();
399
+ const isAwg = info && info.mode === 'amneziawg';
400
+ const cmd = isAwg ? 'awg' : 'wg';
401
+ let r = spawnSync('sudo', [cmd, 'show', 'all', 'dump'], { encoding: 'utf-8' });
402
+ if (r.status !== 0 && isAwg) {
403
+ r = spawnSync('sudo', ['wg', 'show', 'all', 'dump'], { encoding: 'utf-8' });
404
+ }
395
405
  if (r.status !== 0 || !r.stdout.trim()) {
396
406
  L.push(` ${t(D.warning, '⚠')} No WireGuard interface found.`);
397
407
  L.push(` ${mu('Start a WireGuard tunnel first.')}`);