Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 606b3fe89f | |||
| c7340d011a | |||
| 9220ff54aa |
@@ -1,3 +1,7 @@
|
|||||||
|
<p align="center">
|
||||||
|
<img src="favicon.svg" width="120" height="120" alt="GHOSTWIRE logo: the Hannya mask">
|
||||||
|
</p>
|
||||||
|
|
||||||
# GHOSTWIRE
|
# GHOSTWIRE
|
||||||
|
|
||||||
**ゴーストワイヤー** · A self-hosted WireGuard server manager in a single Go
|
**ゴーストワイヤー** · A self-hosted WireGuard server manager in a single Go
|
||||||
@@ -8,6 +12,8 @@ remove), hands out client configs as a download or QR code, and records traffic
|
|||||||
and connection history per peer. There are no install scripts and no
|
and connection history per peer. There are no install scripts and no
|
||||||
dependencies on the server: the binary installs, updates and removes itself.
|
dependencies on the server: the binary installs, updates and removes itself.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **One file of state:** everything lives in `config.json`. The kernel is
|
- **One file of state:** everything lives in `config.json`. The kernel is
|
||||||
@@ -34,6 +40,19 @@ dependencies on the server: the binary installs, updates and removes itself.
|
|||||||
- **HTTPS built in:** Let's Encrypt, a self-signed certificate, your own
|
- **HTTPS built in:** Let's Encrypt, a self-signed certificate, your own
|
||||||
certificate files, or plain HTTP behind a reverse proxy.
|
certificate files, or plain HTTP behind a reverse proxy.
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
|  |  |
|
||||||
|
| **Peers:** status, endpoint, latency and traffic at a glance | **Peer:** traffic, latency, connection history and settings |
|
||||||
|
|  |  |
|
||||||
|
| **Server:** health, address plan, client defaults and firewall | **Settings:** users, web interface and API tokens |
|
||||||
|
|  |  |
|
||||||
|
| **My account:** profile, password and your app tokens | **Sign-in** |
|
||||||
|
|
||||||
|
The screenshots show sample data from the built-in simulator.
|
||||||
|
|
||||||
## Security
|
## Security
|
||||||
|
|
||||||
- **Client private keys are never stored.** A config is shown once, as a
|
- **Client private keys are never stored.** A config is shown once, as a
|
||||||
|
|||||||
@@ -66,6 +66,9 @@ a.brand, a.brand:hover { color: #fff; text-decoration: none; }
|
|||||||
.side .avatar { width: 28px; height: 28px; border-radius: 50%; background: #3a3b41; display: grid; place-items: center; flex: none; font-size: 13px; font-weight: 600; color: #fff; }
|
.side .avatar { width: 28px; height: 28px; border-radius: 50%; background: #3a3b41; display: grid; place-items: center; flex: none; font-size: 13px; font-weight: 600; color: #fff; }
|
||||||
.side .footrow { display: flex; justify-content: space-between; padding: 10px 12px 0; }
|
.side .footrow { display: flex; justify-content: space-between; padding: 10px 12px 0; }
|
||||||
.main { flex: 999 1 560px; min-width: 0; padding: 32px 32px 56px; }
|
.main { flex: 999 1 560px; min-width: 0; padding: 32px 32px 56px; }
|
||||||
|
/* Beside the page (not stacked above it on a phone), the sidebar stays in
|
||||||
|
place while the page scrolls, so the account link is always visible. */
|
||||||
|
@media (min-width: 800px) { .side { position: sticky; top: 0; height: 100vh; height: 100dvh; overflow-y: auto; } }
|
||||||
.wrap { max-width: 1120px; margin: 0 auto; display: flex; flex-direction: column; gap: 20px; }
|
.wrap { max-width: 1120px; margin: 0 auto; display: flex; flex-direction: column; gap: 20px; }
|
||||||
@media (max-width: 640px) { .main { padding: 20px 16px 40px; } }
|
@media (max-width: 640px) { .main { padding: 20px 16px 40px; } }
|
||||||
|
|
||||||
|
|||||||
@@ -518,7 +518,7 @@
|
|||||||
h('span', null, h('strong', null, me.name), h('span', null, 'My account')))),
|
h('span', null, h('strong', null, me.name), h('span', null, 'My account')))),
|
||||||
h('div', { class: 'footrow' },
|
h('div', { class: 'footrow' },
|
||||||
h('button', { type: 'button', onClick: logout }, 'Sign out'),
|
h('button', { type: 'button', onClick: logout }, 'Sign out'),
|
||||||
h('span', null, 'v' + me.version))));
|
h('span', null, 'v' + me.version.replace(/^v/, '')))));
|
||||||
main = h('main', { class: 'main', id: 'main' });
|
main = h('main', { class: 'main', id: 'main' });
|
||||||
app.replaceChildren(h('div', { class: 'shell' }, nav, main));
|
app.replaceChildren(h('div', { class: 'shell' }, nav, main));
|
||||||
refreshSide();
|
refreshSide();
|
||||||
|
|||||||
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 97 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 195 KiB |
|
After Width: | Height: | Size: 96 KiB |
|
After Width: | Height: | Size: 147 KiB |
|
After Width: | Height: | Size: 81 KiB |