wisp-vpn 0.1.0__tar.gz

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.
Files changed (77) hide show
  1. wisp_vpn-0.1.0/LICENSE +21 -0
  2. wisp_vpn-0.1.0/PKG-INFO +241 -0
  3. wisp_vpn-0.1.0/README.md +200 -0
  4. wisp_vpn-0.1.0/pyproject.toml +100 -0
  5. wisp_vpn-0.1.0/setup.cfg +4 -0
  6. wisp_vpn-0.1.0/src/wisp/cli/__init__.py +3 -0
  7. wisp_vpn-0.1.0/src/wisp/cli/__main__.py +10 -0
  8. wisp_vpn-0.1.0/src/wisp/cli/app.py +252 -0
  9. wisp_vpn-0.1.0/src/wisp/cli/screens/__init__.py +15 -0
  10. wisp_vpn-0.1.0/src/wisp/cli/screens/base.py +53 -0
  11. wisp_vpn-0.1.0/src/wisp/cli/screens/config.py +713 -0
  12. wisp_vpn-0.1.0/src/wisp/cli/screens/deploy.py +570 -0
  13. wisp_vpn-0.1.0/src/wisp/cli/screens/main_menu.py +225 -0
  14. wisp_vpn-0.1.0/src/wisp/cli/screens/modal.py +242 -0
  15. wisp_vpn-0.1.0/src/wisp/cli/screens/progress.py +404 -0
  16. wisp_vpn-0.1.0/src/wisp/cli/screens/shutdown.py +305 -0
  17. wisp_vpn-0.1.0/src/wisp/cli/screens/tunnel.py +244 -0
  18. wisp_vpn-0.1.0/src/wisp/cli/state.py +103 -0
  19. wisp_vpn-0.1.0/src/wisp/cli/widgets/__init__.py +0 -0
  20. wisp_vpn-0.1.0/src/wisp/config/__init__.py +3 -0
  21. wisp_vpn-0.1.0/src/wisp/config/constants.py +171 -0
  22. wisp_vpn-0.1.0/src/wisp/config/credentials.py +504 -0
  23. wisp_vpn-0.1.0/src/wisp/config/settings.py +275 -0
  24. wisp_vpn-0.1.0/src/wisp/daemon/__init__.py +14 -0
  25. wisp_vpn-0.1.0/src/wisp/daemon/adapter.py +129 -0
  26. wisp_vpn-0.1.0/src/wisp/daemon/install.py +505 -0
  27. wisp_vpn-0.1.0/src/wisp/daemon/protocol.py +63 -0
  28. wisp_vpn-0.1.0/src/wisp/daemon/server.py +122 -0
  29. wisp_vpn-0.1.0/src/wisp/daemon/transport.py +381 -0
  30. wisp_vpn-0.1.0/src/wisp/daemon/windows_service.py +84 -0
  31. wisp_vpn-0.1.0/src/wisp/data/wireguard-server-install.sh +709 -0
  32. wisp_vpn-0.1.0/src/wisp/data/wisp.service.tpl +12 -0
  33. wisp_vpn-0.1.0/src/wisp/data/wisp.socket +11 -0
  34. wisp_vpn-0.1.0/src/wisp/main.py +548 -0
  35. wisp_vpn-0.1.0/src/wisp/providers/__init__.py +31 -0
  36. wisp_vpn-0.1.0/src/wisp/providers/aws/__init__.py +3 -0
  37. wisp_vpn-0.1.0/src/wisp/providers/aws/constants.py +9 -0
  38. wisp_vpn-0.1.0/src/wisp/providers/aws/provider.py +62 -0
  39. wisp_vpn-0.1.0/src/wisp/providers/aws/pulumi.py +267 -0
  40. wisp_vpn-0.1.0/src/wisp/providers/base.py +148 -0
  41. wisp_vpn-0.1.0/src/wisp/providers/gcp/__init__.py +5 -0
  42. wisp_vpn-0.1.0/src/wisp/providers/gcp/constants.py +26 -0
  43. wisp_vpn-0.1.0/src/wisp/providers/gcp/provider.py +84 -0
  44. wisp_vpn-0.1.0/src/wisp/providers/gcp/pulumi.py +271 -0
  45. wisp_vpn-0.1.0/src/wisp/providers/oci/__init__.py +13 -0
  46. wisp_vpn-0.1.0/src/wisp/providers/oci/constants.py +19 -0
  47. wisp_vpn-0.1.0/src/wisp/providers/oci/provider.py +99 -0
  48. wisp_vpn-0.1.0/src/wisp/providers/oci/pulumi.py +470 -0
  49. wisp_vpn-0.1.0/src/wisp/providers/pulumi_base.py +293 -0
  50. wisp_vpn-0.1.0/src/wisp/providers/vps.py +269 -0
  51. wisp_vpn-0.1.0/src/wisp/schemas.py +30 -0
  52. wisp_vpn-0.1.0/src/wisp/session.py +496 -0
  53. wisp_vpn-0.1.0/src/wisp/utils/__init__.py +38 -0
  54. wisp_vpn-0.1.0/src/wisp/utils/logger.py +106 -0
  55. wisp_vpn-0.1.0/src/wisp/utils/platform.py +68 -0
  56. wisp_vpn-0.1.0/src/wisp/utils/pulumi.py +130 -0
  57. wisp_vpn-0.1.0/src/wisp/utils/randoms.py +11 -0
  58. wisp_vpn-0.1.0/src/wisp/utils/watchdog.py +278 -0
  59. wisp_vpn-0.1.0/src/wisp/wireguard/__init__.py +17 -0
  60. wisp_vpn-0.1.0/src/wisp/wireguard/local_client.py +32 -0
  61. wisp_vpn-0.1.0/src/wisp/wireguard/remote_server.py +421 -0
  62. wisp_vpn-0.1.0/src/wisp_vpn.egg-info/PKG-INFO +241 -0
  63. wisp_vpn-0.1.0/src/wisp_vpn.egg-info/SOURCES.txt +75 -0
  64. wisp_vpn-0.1.0/src/wisp_vpn.egg-info/dependency_links.txt +1 -0
  65. wisp_vpn-0.1.0/src/wisp_vpn.egg-info/entry_points.txt +2 -0
  66. wisp_vpn-0.1.0/src/wisp_vpn.egg-info/requires.txt +16 -0
  67. wisp_vpn-0.1.0/src/wisp_vpn.egg-info/top_level.txt +1 -0
  68. wisp_vpn-0.1.0/tests/test_config_screen.py +218 -0
  69. wisp_vpn-0.1.0/tests/test_daemon.py +53 -0
  70. wisp_vpn-0.1.0/tests/test_daemon_install.py +515 -0
  71. wisp_vpn-0.1.0/tests/test_focus_order.py +126 -0
  72. wisp_vpn-0.1.0/tests/test_layout.py +168 -0
  73. wisp_vpn-0.1.0/tests/test_logger.py +81 -0
  74. wisp_vpn-0.1.0/tests/test_paths.py +129 -0
  75. wisp_vpn-0.1.0/tests/test_progress_screen.py +84 -0
  76. wisp_vpn-0.1.0/tests/test_settings.py +42 -0
  77. wisp_vpn-0.1.0/tests/test_tunnel_screen.py +201 -0
wisp_vpn-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Juan D. Perez
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,241 @@
1
+ Metadata-Version: 2.4
2
+ Name: wisp-vpn
3
+ Version: 0.1.0
4
+ Summary: Wisp — ephemeral WireGuard VPNs on your own cloud
5
+ Author-email: dosquisd <pereznjuandiego@gmail.com>, SantiagoQP31 <santiagoqp3103@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/dosquisd/wisp-vpn
8
+ Project-URL: Issues, https://github.com/dosquisd/wisp-vpn/issues
9
+ Keywords: wireguard,vpn,ephemeral,pulumi,aws,oci,gcp,tui
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Intended Audience :: System Administrators
14
+ Classifier: Operating System :: POSIX :: Linux
15
+ Classifier: Operating System :: Microsoft :: Windows
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Topic :: Internet
20
+ Classifier: Topic :: System :: Networking
21
+ Classifier: Topic :: System :: Systems Administration
22
+ Classifier: Topic :: Utilities
23
+ Requires-Python: >=3.14
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: boto3>=1.43.83
27
+ Requires-Dist: google-cloud-compute>=1.54.0
28
+ Requires-Dist: oci>=2.100.0
29
+ Requires-Dist: paramiko>=5.0.0
30
+ Requires-Dist: platformdirs>=4.11.7
31
+ Requires-Dist: pulumi<4.0.0,>=3.0.0
32
+ Requires-Dist: pulumi-aws<8.0.0,>=7.0.0
33
+ Requires-Dist: pulumi-gcp>=9.37.0
34
+ Requires-Dist: pulumi-oci>=4.22.0
35
+ Requires-Dist: pulumi-tls>=5.5.1
36
+ Requires-Dist: pywin32>=312; sys_platform == "win32"
37
+ Requires-Dist: textual>=8.2.8
38
+ Requires-Dist: tomli-w>=1.2.0
39
+ Requires-Dist: tqdm>=4.70.0
40
+ Dynamic: license-file
41
+
42
+ <div align="center">
43
+
44
+ ```text
45
+ ██╗ ██╗██╗███████╗██████╗
46
+ ██║ ██║██║██╔════╝██╔══██╗
47
+ ██║ █╗ ██║██║███████╗██████╔╝
48
+ ██║███╗██║██║╚════██║██╔═══╝
49
+ ╚███╔███╔╝██║███████║██║
50
+ ╚══╝╚══╝ ╚═╝╚══════╝╚═╝
51
+ ```
52
+
53
+ **A VPN for the sessions you actually need.**
54
+
55
+ A monthly subscription bills you for every minute of the month so you can use
56
+ thirty. Wisp spins up a WireGuard server in your own cloud account, connects
57
+ you, and destroys it when you're done — measured: **~30 seconds to a working
58
+ tunnel** on your own VPS, ~2–3 minutes on a cloud provider (the VM boot is
59
+ the slow part), and a typical session costs fractions of a cent on
60
+ pay-as-you-go pricing.
61
+
62
+ <img src="https://raw.githubusercontent.com/dosquisd/wisp-vpn/main/.github/assets/demo-tui.gif" alt="Wisp TUI — deploy, tunnel, destroy" width="100%">
63
+
64
+ [![Python](https://img.shields.io/badge/python-3.14%2B-3776AB?logo=python&logoColor=white)](https://github.com/dosquisd/wisp-vpn/pyproject.toml)
65
+ [![License](https://img.shields.io/badge/license-MIT-10b981)]([LICENSE](https://github.com/dosquisd/wisp-vpn/tree/main/LICENSE))
66
+ [![Platform](https://img.shields.io/badge/platform-linux%20%7C%20windows-64748b)](https://github.com/dosquisd/wisp-vpn/tree/main/docs/installation.md)
67
+ [![Providers](https://img.shields.io/badge/providers-aws%20%7C%20oci%20%7C%20gcp%20%7C%20vps-f59e0b)](#supported-providers)
68
+
69
+ [Installation](#install) · [Quick start](#quick-start) · [Documentation](https://github.com/dosquisd/wisp-vpn/tree/main/docs/README.md) · [Contributing](https://github.com/dosquisd/wisp-vpn/tree/main/docs/contributing.md)
70
+
71
+ </div>
72
+
73
+ ---
74
+
75
+ ## Why Wisp
76
+
77
+ | | Subscription VPN | Always-on VPS | Wisp |
78
+ | --- | --- | --- | --- |
79
+ | Billing | Flat, monthly | 24/7, whether you use it or not | Per session — fractions of a cent |
80
+ | Who runs the server | The provider | You | You, briefly |
81
+ | Upkeep | None | Patches, a box to forget | None — it's destroyed |
82
+ | Exit IP | Shared pool | Dedicated, tied to your account | Dedicated, tied to your account |
83
+
84
+ **Who it's for:** developers who already have a cloud account (or a spare VPS)
85
+ and use a VPN occasionally — a few sessions a month, not all day every day.
86
+
87
+ **Who it's not for:** if you need an anonymous exit IP, look elsewhere — Wisp's
88
+ exit IP belongs to *your* cloud account or *your* VPS, which is the point.
89
+
90
+ - **Designed so nothing outlives your session.** The VM is destroyed on exit —
91
+ `q`, `Ctrl+C`, closing the terminal — and an optional TTL watchdog tears down
92
+ forgotten sessions. (The watchdog timer runs on your machine: if that machine
93
+ is off or suspended when the TTL expires, the session survives until the next
94
+ `wisp gc` — and `wisp gc --destroy` cleans up anything a crash left behind.)
95
+ - **Nothing to clean up by hand.** Orphan detection and `wisp gc` catch
96
+ anything a crash left behind.
97
+ - **Bring your own server.** Already have a VPS? Skip the cloud entirely —
98
+ Wisp configures WireGuard on it over SSH and cleans up after itself.
99
+ - **No sudo at runtime.** The privileged daemon owns the local WireGuard
100
+ interface; the CLI/TUI never elevate after installation.
101
+
102
+ ## Features
103
+
104
+ | | Feature | What it means |
105
+ | --- | --- | --- |
106
+ | **TUI** | Interactive terminal UI | Full deploy wizard, live progress, one-key teardown |
107
+ | **CLI** | Scriptable subcommands | `deploy`, `destroy`, `regions`, `gc` — JSON output, `tqdm` progress |
108
+ | **Multi-cloud** | AWS · OCI · GCP | Provisioned via Pulumi; RSA 4096 SSH keys per session |
109
+ | **BYO server** | Custom VPS | No cloud account needed — SSH into a host you own |
110
+ | **Ephemeral** | Session guard | `q`/`Ctrl+C`/`esc`/signals/`atexit` all release the resources, exactly once |
111
+ | **Watchdog** | TTL auto-destroy | Optional OS-level timer runs `wisp destroy` if the session outlives its TTL |
112
+ | **Garbage collection** | `wisp gc` | Finds and destroys leftover stacks from crashed sessions |
113
+ | **No sudo at runtime** | Privileged daemon | After installation the CLI/TUI never elevate; a local daemon owns `wg-quick` |
114
+ | **Live installer output** | Progress streaming | The remote WireGuard install streams into the progress bar — no frozen screens |
115
+
116
+ ## How it works
117
+
118
+ ```text
119
+ Wisp CLI/TUI ──Pulumi──▶ Cloud VM (AWS EC2 / OCI Compute / GCP + WireGuard)
120
+ │ ▲ │
121
+ │ └──── Paramiko (SSH) ────┘ installs & configures the server
122
+ ▼
123
+ wisp daemon (privileged) ──wg-quick──▶ local WireGuard interface (wg0)
124
+ ```
125
+
126
+ The client-side privileged daemon means the unprivileged CLI/TUI never needs
127
+ `sudo` to bring the local tunnel up or down; it talks to the daemon over a
128
+ group-restricted Unix socket (Linux) or Named Pipe (Windows) instead. The
129
+ session guard arms *before* provisioning starts, so a crashed TUI still
130
+ leaves a marker behind — the next run offers to clean it up, and the optional
131
+ TTL watchdog handles it even if nobody reopens Wisp.
132
+
133
+ ## Install (from source)
134
+
135
+ Requirements: Python **3.14+** (if you install via `uv`, it manages Python for
136
+ you — you never see this requirement), the
137
+ [Pulumi CLI](https://www.pulumi.com/docs/install/), and WireGuard tools — both
138
+ installed by the setup script.
139
+
140
+ ```bash
141
+ # 0. Clone and enter the repo
142
+ git clone https://github.com/dosquisd/wisp.git && cd wisp
143
+
144
+ # 1. Install the CLI (isolated tool venv)
145
+ uv tool install .
146
+
147
+ # 2. Install the privileged local daemon (needs root/administrator)
148
+ sudo ./setup.sh # Linux; or: sudo ./setup.sh --skip-pulumi
149
+ ./setup.ps1 # Windows (self-elevates to administrator)
150
+ ```
151
+
152
+ Then log out and back in (or `newgrp wisp`) so your `wisp` group membership
153
+ takes effect. macOS is not supported yet (the daemon adapter is not
154
+ implemented — see `MacOSAdapter` in
155
+ [`daemon/adapter.py`](https://github.com/dosquisd/wisp-vpn/src/wisp/daemon/adapter.py)). A published PyPI package
156
+ with a one-command installer is planned.
157
+
158
+ ## Quick start
159
+
160
+ ```bash
161
+ # 1. Authenticate the Pulumi backend (the deployment state lives there)
162
+ pulumi login
163
+
164
+ # 2. Configure your cloud credentials (standard SDK chains — see docs)
165
+ aws configure # or ~/.oci/config, or [gcp] in wisp.toml
166
+
167
+ # 3. Launch the TUI (or use the CLI directly)
168
+ wisp
169
+ ```
170
+
171
+ ```bash
172
+ # CLI equivalent — deploy, tunnel, destroy:
173
+ wisp deploy aws --region eu-west-1 # provisions VM + WireGuard, connects you
174
+ wisp destroy aws --region eu-west-1 # tears it all down
175
+ ```
176
+
177
+ <!-- TODO: record the CLI demo GIF — deploy + destroy, 15-20s.
178
+ <img src=".github/assets/demo-cli.gif" alt="wisp deploy / destroy on the CLI" width="100%">
179
+ -->
180
+
181
+ ## CLI cheat sheet
182
+
183
+ | Command | What it does |
184
+ | --- | --- |
185
+ | `wisp` | Launch the interactive TUI |
186
+ | `wisp deploy <provider> [-r region]` | Deploy a VPN (add `--ttl 120`, `--wireguard-port 51820`) |
187
+ | `wisp deploy vps --host <ip> --ssh-user root` | Configure WireGuard on a server you own |
188
+ | `wisp destroy <provider> [-r region]` | Tear down the tunnel + all cloud resources |
189
+ | `wisp regions <provider>` | List available regions (JSON) |
190
+ | `wisp gc [--refresh] [--destroy]` | Find and clean up leftover stacks |
191
+
192
+ ## Supported providers
193
+
194
+ | Provider | Provisioning | Credentials |
195
+ | --- | --- | --- |
196
+ | **AWS** | Pulumi (`t3.micro`, most-recent Ubuntu AMI) | Standard AWS chain (`aws configure`, env vars) |
197
+ | **OCI** | Pulumi (`VM.Standard.A1.Flex`, Ubuntu 24.04) | `~/.oci/config` profile |
198
+ | **GCP** | Pulumi (SPOT `e2-micro`, default network) | Service account key or ADC |
199
+ | **Custom VPS** | None — SSH into a host you already own | Your own SSH key (`[vps]` in `wisp.toml`) |
200
+
201
+ > **Note:** as of this writing, Wisp ships with support for `aws`, `oci`,
202
+ > `gcp`, and `vps`. The provider layer is designed to be extensible — see
203
+ > `ProviderEnum`
204
+ > ([`src/wisp/providers/base.py`](https://github.com/dosquisd/wisp-vpn/src/wisp/providers/base.py)) and
205
+ > `PROVIDERS_MAP`
206
+ > ([`src/wisp/providers/__init__.py`](https://github.com/dosquisd/wisp-vpn/src/wisp/providers/__init__.py)), or
207
+ > the [contributing guide](https://github.com/dosquisd/wisp-vpn/tree/main/docs/contributing.md#adding-a-cloud-provider) to
208
+ > add your own.
209
+
210
+ ## Documentation
211
+
212
+ Full documentation lives in [`docs/`](https://github.com/dosquisd/wisp-vpn/tree/main/docs/README.md):
213
+
214
+ | | |
215
+ | --- | --- |
216
+ | [Architecture](https://github.com/dosquisd/wisp-vpn/tree/main/docs/architecture.md) | High-level design, module map, runtime data flow |
217
+ | [Installation](https://github.com/dosquisd/wisp-vpn/tree/main/docs/installation.md) | System requirements, `setup.sh` / `setup.ps1`, daemon |
218
+ | [Usage](https://github.com/dosquisd/wisp-vpn/tree/main/docs/usage.md) | Full CLI and TUI reference |
219
+ | [Configuration](https://github.com/dosquisd/wisp-vpn/tree/main/docs/configuration.md) | `wisp.toml`, `WispConfig`, per-provider settings |
220
+ | [Deployment flow](https://github.com/dosquisd/wisp-vpn/tree/main/docs/deployment-flow.md) | Step-by-step deploy and destroy |
221
+ | [Security model](https://github.com/dosquisd/wisp-vpn/tree/main/docs/security.md) | Keys, firewall, socket permissions, trust boundaries |
222
+ | [Components reference](https://github.com/dosquisd/wisp-vpn/tree/main/docs/components/README.md) | Per-module reference |
223
+ | [Contributing](https://github.com/dosquisd/wisp-vpn/tree/main/docs/contributing.md) | Adding a provider, style, commits |
224
+
225
+ ## Security
226
+
227
+ Keys are per-session RSA 4096 pairs, stored `0600` and removed on destroy.
228
+ The daemon socket is group-restricted (`root:wisp`). Cloud credentials are
229
+ never stored by Wisp — each provider uses its standard SDK chain (the only
230
+ file Wisp ever touches is the optional `[gcp].credentials_path`, which you
231
+ point at; Wisp reads it, it does not manage it). Read the full
232
+ [security model](https://github.com/dosquisd/wisp-vpn/tree/main/docs/security.md).
233
+
234
+ ## Contributing
235
+
236
+ PRs against `main`. The provider layer is the easiest entry point — see
237
+ [adding a cloud provider](https://github.com/dosquisd/wisp-vpn/tree/main/docs/contributing.md#adding-a-cloud-provider).
238
+
239
+ ## License
240
+
241
+ [MIT](https://github.com/dosquisd/wisp-vpn/tree/main/LICENSE) — Copyright (c) 2026 Juan D. Perez
@@ -0,0 +1,200 @@
1
+ <div align="center">
2
+
3
+ ```text
4
+ ██╗ ██╗██╗███████╗██████╗
5
+ ██║ ██║██║██╔════╝██╔══██╗
6
+ ██║ █╗ ██║██║███████╗██████╔╝
7
+ ██║███╗██║██║╚════██║██╔═══╝
8
+ ╚███╔███╔╝██║███████║██║
9
+ ╚══╝╚══╝ ╚═╝╚══════╝╚═╝
10
+ ```
11
+
12
+ **A VPN for the sessions you actually need.**
13
+
14
+ A monthly subscription bills you for every minute of the month so you can use
15
+ thirty. Wisp spins up a WireGuard server in your own cloud account, connects
16
+ you, and destroys it when you're done — measured: **~30 seconds to a working
17
+ tunnel** on your own VPS, ~2–3 minutes on a cloud provider (the VM boot is
18
+ the slow part), and a typical session costs fractions of a cent on
19
+ pay-as-you-go pricing.
20
+
21
+ <img src="https://raw.githubusercontent.com/dosquisd/wisp-vpn/main/.github/assets/demo-tui.gif" alt="Wisp TUI — deploy, tunnel, destroy" width="100%">
22
+
23
+ [![Python](https://img.shields.io/badge/python-3.14%2B-3776AB?logo=python&logoColor=white)](https://github.com/dosquisd/wisp-vpn/pyproject.toml)
24
+ [![License](https://img.shields.io/badge/license-MIT-10b981)]([LICENSE](https://github.com/dosquisd/wisp-vpn/tree/main/LICENSE))
25
+ [![Platform](https://img.shields.io/badge/platform-linux%20%7C%20windows-64748b)](https://github.com/dosquisd/wisp-vpn/tree/main/docs/installation.md)
26
+ [![Providers](https://img.shields.io/badge/providers-aws%20%7C%20oci%20%7C%20gcp%20%7C%20vps-f59e0b)](#supported-providers)
27
+
28
+ [Installation](#install) · [Quick start](#quick-start) · [Documentation](https://github.com/dosquisd/wisp-vpn/tree/main/docs/README.md) · [Contributing](https://github.com/dosquisd/wisp-vpn/tree/main/docs/contributing.md)
29
+
30
+ </div>
31
+
32
+ ---
33
+
34
+ ## Why Wisp
35
+
36
+ | | Subscription VPN | Always-on VPS | Wisp |
37
+ | --- | --- | --- | --- |
38
+ | Billing | Flat, monthly | 24/7, whether you use it or not | Per session — fractions of a cent |
39
+ | Who runs the server | The provider | You | You, briefly |
40
+ | Upkeep | None | Patches, a box to forget | None — it's destroyed |
41
+ | Exit IP | Shared pool | Dedicated, tied to your account | Dedicated, tied to your account |
42
+
43
+ **Who it's for:** developers who already have a cloud account (or a spare VPS)
44
+ and use a VPN occasionally — a few sessions a month, not all day every day.
45
+
46
+ **Who it's not for:** if you need an anonymous exit IP, look elsewhere — Wisp's
47
+ exit IP belongs to *your* cloud account or *your* VPS, which is the point.
48
+
49
+ - **Designed so nothing outlives your session.** The VM is destroyed on exit —
50
+ `q`, `Ctrl+C`, closing the terminal — and an optional TTL watchdog tears down
51
+ forgotten sessions. (The watchdog timer runs on your machine: if that machine
52
+ is off or suspended when the TTL expires, the session survives until the next
53
+ `wisp gc` — and `wisp gc --destroy` cleans up anything a crash left behind.)
54
+ - **Nothing to clean up by hand.** Orphan detection and `wisp gc` catch
55
+ anything a crash left behind.
56
+ - **Bring your own server.** Already have a VPS? Skip the cloud entirely —
57
+ Wisp configures WireGuard on it over SSH and cleans up after itself.
58
+ - **No sudo at runtime.** The privileged daemon owns the local WireGuard
59
+ interface; the CLI/TUI never elevate after installation.
60
+
61
+ ## Features
62
+
63
+ | | Feature | What it means |
64
+ | --- | --- | --- |
65
+ | **TUI** | Interactive terminal UI | Full deploy wizard, live progress, one-key teardown |
66
+ | **CLI** | Scriptable subcommands | `deploy`, `destroy`, `regions`, `gc` — JSON output, `tqdm` progress |
67
+ | **Multi-cloud** | AWS · OCI · GCP | Provisioned via Pulumi; RSA 4096 SSH keys per session |
68
+ | **BYO server** | Custom VPS | No cloud account needed — SSH into a host you own |
69
+ | **Ephemeral** | Session guard | `q`/`Ctrl+C`/`esc`/signals/`atexit` all release the resources, exactly once |
70
+ | **Watchdog** | TTL auto-destroy | Optional OS-level timer runs `wisp destroy` if the session outlives its TTL |
71
+ | **Garbage collection** | `wisp gc` | Finds and destroys leftover stacks from crashed sessions |
72
+ | **No sudo at runtime** | Privileged daemon | After installation the CLI/TUI never elevate; a local daemon owns `wg-quick` |
73
+ | **Live installer output** | Progress streaming | The remote WireGuard install streams into the progress bar — no frozen screens |
74
+
75
+ ## How it works
76
+
77
+ ```text
78
+ Wisp CLI/TUI ──Pulumi──▶ Cloud VM (AWS EC2 / OCI Compute / GCP + WireGuard)
79
+ │ ▲ │
80
+ │ └──── Paramiko (SSH) ────┘ installs & configures the server
81
+ ▼
82
+ wisp daemon (privileged) ──wg-quick──▶ local WireGuard interface (wg0)
83
+ ```
84
+
85
+ The client-side privileged daemon means the unprivileged CLI/TUI never needs
86
+ `sudo` to bring the local tunnel up or down; it talks to the daemon over a
87
+ group-restricted Unix socket (Linux) or Named Pipe (Windows) instead. The
88
+ session guard arms *before* provisioning starts, so a crashed TUI still
89
+ leaves a marker behind — the next run offers to clean it up, and the optional
90
+ TTL watchdog handles it even if nobody reopens Wisp.
91
+
92
+ ## Install (from source)
93
+
94
+ Requirements: Python **3.14+** (if you install via `uv`, it manages Python for
95
+ you — you never see this requirement), the
96
+ [Pulumi CLI](https://www.pulumi.com/docs/install/), and WireGuard tools — both
97
+ installed by the setup script.
98
+
99
+ ```bash
100
+ # 0. Clone and enter the repo
101
+ git clone https://github.com/dosquisd/wisp.git && cd wisp
102
+
103
+ # 1. Install the CLI (isolated tool venv)
104
+ uv tool install .
105
+
106
+ # 2. Install the privileged local daemon (needs root/administrator)
107
+ sudo ./setup.sh # Linux; or: sudo ./setup.sh --skip-pulumi
108
+ ./setup.ps1 # Windows (self-elevates to administrator)
109
+ ```
110
+
111
+ Then log out and back in (or `newgrp wisp`) so your `wisp` group membership
112
+ takes effect. macOS is not supported yet (the daemon adapter is not
113
+ implemented — see `MacOSAdapter` in
114
+ [`daemon/adapter.py`](https://github.com/dosquisd/wisp-vpn/src/wisp/daemon/adapter.py)). A published PyPI package
115
+ with a one-command installer is planned.
116
+
117
+ ## Quick start
118
+
119
+ ```bash
120
+ # 1. Authenticate the Pulumi backend (the deployment state lives there)
121
+ pulumi login
122
+
123
+ # 2. Configure your cloud credentials (standard SDK chains — see docs)
124
+ aws configure # or ~/.oci/config, or [gcp] in wisp.toml
125
+
126
+ # 3. Launch the TUI (or use the CLI directly)
127
+ wisp
128
+ ```
129
+
130
+ ```bash
131
+ # CLI equivalent — deploy, tunnel, destroy:
132
+ wisp deploy aws --region eu-west-1 # provisions VM + WireGuard, connects you
133
+ wisp destroy aws --region eu-west-1 # tears it all down
134
+ ```
135
+
136
+ <!-- TODO: record the CLI demo GIF — deploy + destroy, 15-20s.
137
+ <img src=".github/assets/demo-cli.gif" alt="wisp deploy / destroy on the CLI" width="100%">
138
+ -->
139
+
140
+ ## CLI cheat sheet
141
+
142
+ | Command | What it does |
143
+ | --- | --- |
144
+ | `wisp` | Launch the interactive TUI |
145
+ | `wisp deploy <provider> [-r region]` | Deploy a VPN (add `--ttl 120`, `--wireguard-port 51820`) |
146
+ | `wisp deploy vps --host <ip> --ssh-user root` | Configure WireGuard on a server you own |
147
+ | `wisp destroy <provider> [-r region]` | Tear down the tunnel + all cloud resources |
148
+ | `wisp regions <provider>` | List available regions (JSON) |
149
+ | `wisp gc [--refresh] [--destroy]` | Find and clean up leftover stacks |
150
+
151
+ ## Supported providers
152
+
153
+ | Provider | Provisioning | Credentials |
154
+ | --- | --- | --- |
155
+ | **AWS** | Pulumi (`t3.micro`, most-recent Ubuntu AMI) | Standard AWS chain (`aws configure`, env vars) |
156
+ | **OCI** | Pulumi (`VM.Standard.A1.Flex`, Ubuntu 24.04) | `~/.oci/config` profile |
157
+ | **GCP** | Pulumi (SPOT `e2-micro`, default network) | Service account key or ADC |
158
+ | **Custom VPS** | None — SSH into a host you already own | Your own SSH key (`[vps]` in `wisp.toml`) |
159
+
160
+ > **Note:** as of this writing, Wisp ships with support for `aws`, `oci`,
161
+ > `gcp`, and `vps`. The provider layer is designed to be extensible — see
162
+ > `ProviderEnum`
163
+ > ([`src/wisp/providers/base.py`](https://github.com/dosquisd/wisp-vpn/src/wisp/providers/base.py)) and
164
+ > `PROVIDERS_MAP`
165
+ > ([`src/wisp/providers/__init__.py`](https://github.com/dosquisd/wisp-vpn/src/wisp/providers/__init__.py)), or
166
+ > the [contributing guide](https://github.com/dosquisd/wisp-vpn/tree/main/docs/contributing.md#adding-a-cloud-provider) to
167
+ > add your own.
168
+
169
+ ## Documentation
170
+
171
+ Full documentation lives in [`docs/`](https://github.com/dosquisd/wisp-vpn/tree/main/docs/README.md):
172
+
173
+ | | |
174
+ | --- | --- |
175
+ | [Architecture](https://github.com/dosquisd/wisp-vpn/tree/main/docs/architecture.md) | High-level design, module map, runtime data flow |
176
+ | [Installation](https://github.com/dosquisd/wisp-vpn/tree/main/docs/installation.md) | System requirements, `setup.sh` / `setup.ps1`, daemon |
177
+ | [Usage](https://github.com/dosquisd/wisp-vpn/tree/main/docs/usage.md) | Full CLI and TUI reference |
178
+ | [Configuration](https://github.com/dosquisd/wisp-vpn/tree/main/docs/configuration.md) | `wisp.toml`, `WispConfig`, per-provider settings |
179
+ | [Deployment flow](https://github.com/dosquisd/wisp-vpn/tree/main/docs/deployment-flow.md) | Step-by-step deploy and destroy |
180
+ | [Security model](https://github.com/dosquisd/wisp-vpn/tree/main/docs/security.md) | Keys, firewall, socket permissions, trust boundaries |
181
+ | [Components reference](https://github.com/dosquisd/wisp-vpn/tree/main/docs/components/README.md) | Per-module reference |
182
+ | [Contributing](https://github.com/dosquisd/wisp-vpn/tree/main/docs/contributing.md) | Adding a provider, style, commits |
183
+
184
+ ## Security
185
+
186
+ Keys are per-session RSA 4096 pairs, stored `0600` and removed on destroy.
187
+ The daemon socket is group-restricted (`root:wisp`). Cloud credentials are
188
+ never stored by Wisp — each provider uses its standard SDK chain (the only
189
+ file Wisp ever touches is the optional `[gcp].credentials_path`, which you
190
+ point at; Wisp reads it, it does not manage it). Read the full
191
+ [security model](https://github.com/dosquisd/wisp-vpn/tree/main/docs/security.md).
192
+
193
+ ## Contributing
194
+
195
+ PRs against `main`. The provider layer is the easiest entry point — see
196
+ [adding a cloud provider](https://github.com/dosquisd/wisp-vpn/tree/main/docs/contributing.md#adding-a-cloud-provider).
197
+
198
+ ## License
199
+
200
+ [MIT](https://github.com/dosquisd/wisp-vpn/tree/main/LICENSE) — Copyright (c) 2026 Juan D. Perez
@@ -0,0 +1,100 @@
1
+ [project]
2
+ name = "wisp-vpn"
3
+ version = "0.1.0"
4
+ description = "Wisp — ephemeral WireGuard VPNs on your own cloud"
5
+ readme = "README.md"
6
+ requires-python = ">=3.14"
7
+ authors = [
8
+ { name="dosquisd", email="pereznjuandiego@gmail.com" },
9
+ { name="SantiagoQP31", email="santiagoqp3103@gmail.com" },
10
+ ]
11
+ dependencies = [
12
+ "boto3>=1.43.83",
13
+ "google-cloud-compute>=1.54.0",
14
+ "oci>=2.100.0",
15
+ "paramiko>=5.0.0",
16
+ "platformdirs>=4.11.7",
17
+ "pulumi>=3.0.0,<4.0.0",
18
+ "pulumi-aws>=7.0.0,<8.0.0",
19
+ "pulumi-gcp>=9.37.0",
20
+ "pulumi-oci>=4.22.0",
21
+ "pulumi-tls>=5.5.1",
22
+ "pywin32>=312 ; sys_platform == 'win32'",
23
+ "textual>=8.2.8",
24
+ "tomli-w>=1.2.0",
25
+ "tqdm>=4.70.0",
26
+ ]
27
+ license = "MIT"
28
+ license-files = ["LICEN[CS]E*"]
29
+ keywords = ["wireguard", "vpn", "ephemeral", "pulumi", "aws", "oci", "gcp", "tui"]
30
+ classifiers = [
31
+ "Development Status :: 3 - Alpha",
32
+ "Environment :: Console",
33
+ "Intended Audience :: Developers",
34
+ "Intended Audience :: System Administrators",
35
+ "Operating System :: POSIX :: Linux",
36
+ "Operating System :: Microsoft :: Windows",
37
+ "Programming Language :: Python :: 3",
38
+ "Programming Language :: Python :: 3 :: Only",
39
+ "Programming Language :: Python :: 3.14",
40
+ "Topic :: Internet",
41
+ "Topic :: System :: Networking",
42
+ "Topic :: System :: Systems Administration",
43
+ "Topic :: Utilities",
44
+ ]
45
+
46
+ [project.urls]
47
+ Homepage = "https://github.com/dosquisd/wisp-vpn"
48
+ Issues = "https://github.com/dosquisd/wisp-vpn/issues"
49
+
50
+ [build-system]
51
+ requires = ["setuptools>=84.0.0"]
52
+ build-backend = "setuptools.build_meta"
53
+
54
+ [tool.setuptools.package-data]
55
+ wisp = ["data/*.sh", "data/*.tpl", "data/wisp.socket"]
56
+
57
+ # Use `uv tool install --editable .`
58
+ [project.scripts]
59
+ wisp = "wisp.main:main"
60
+
61
+ [tool.uv]
62
+ package = true
63
+
64
+ [tool.ruff]
65
+ exclude = [".git", ".pytest_cache", ".venv"]
66
+ line-length = 88
67
+ indent-width = 4
68
+ target-version = "py314"
69
+
70
+ [tool.ruff.lint]
71
+ select = ["E4", "E7", "E9", "F"]
72
+ fixable = ["ALL"]
73
+ extend-select = ["I"]
74
+
75
+ [tool.ruff.format]
76
+ quote-style = "double"
77
+ indent-style = "space"
78
+ line-ending = "auto"
79
+
80
+ [dependency-groups]
81
+ dev = [
82
+ "pytest>=8.3.4",
83
+ "pytest-asyncio>=0.24.0",
84
+ "ruff>=0.16.10",
85
+ ]
86
+
87
+ [tool.pytest.ini_options]
88
+ asyncio_mode = "auto"
89
+ testpaths = ["tests"]
90
+
91
+ [[tool.uv.index]]
92
+ name = "testpypi"
93
+ url = "https://test.pypi.org/simple/"
94
+ publish-url = "https://test.pypi.org/legacy/"
95
+ explicit = true
96
+
97
+ # How to install the package from TestPyPI and PyPI:
98
+ # uv pip install --index-url https://test.pypi.org/simple/ \
99
+ # --extra-index-url https://pypi.org/simple/ \
100
+ # wisp-vpn
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ from wisp.cli.app import WispApp
2
+
3
+ __all__ = ["WispApp"]
@@ -0,0 +1,10 @@
1
+ from wisp.cli.app import WispApp
2
+
3
+
4
+ def main() -> None:
5
+ app = WispApp()
6
+ app.run()
7
+
8
+
9
+ if __name__ == "__main__":
10
+ main()