Rename the project to ihasvpn

WGX shares its name with several other WireGuard tools, so the project
becomes ihasvpn, alongside ihasmail.

- Module github.com/Coffey-Labs/ihasvpn, command cmd/ihasvpn, image
  ghcr.io/coffey-labs/ihasvpn.
- Environment variables move from WGX_* to IHASVPN_*. The default database
  is ihasvpn.db, the nftables table is `ihasvpn`, metrics are ihasvpn_*, and
  the session cookie and theme key are renamed, so existing sessions end.
- The mark is the ihasmail cat peeking over the edge of a shield, drawn as
  a vector. docs/brand/generate.py builds the mark, mono mark, wordmarks,
  social card, favicons and app icons from that one drawing.
- The console takes ihasmail's palette: the ihasmail.org teal-navy for dark,
  its contrast-checked light tiers with the site's light accent, received
  traffic in the cat's orange and sent in teal. The wordmark weight and
  font stack follow ihasmail.org.
- Detail values wrap at spaces before breaking inside an address, so an
  IPv6 tunnel address no longer splits mid-number.
- The README history note about the earlier WGX installer is gone with the
  name it explained. Screenshots retaken.
This commit is contained in:
2026-09-12 23:48:36 -07:00
parent e807896806
commit 02e7993c87
74 changed files with 529 additions and 369 deletions
+13 -13
View File
@@ -8,8 +8,8 @@ import (
"strings"
"time"
"github.com/Coffey-Labs/WGX/internal/engine"
"github.com/Coffey-Labs/WGX/internal/store"
"github.com/Coffey-Labs/ihasvpn/internal/engine"
"github.com/Coffey-Labs/ihasvpn/internal/store"
)
type healthBody struct {
@@ -192,7 +192,7 @@ func safeFilename(name string) string {
}
out := b.String()
if out == "" {
out = "wgx"
out = "ihasvpn"
}
if len(out) > 15 {
// wg-quick derives the interface name from the file name and caps it
@@ -374,28 +374,28 @@ func (s *Server) handleMetrics(w http.ResponseWriter, r *http.Request) {
}
w.Header().Set("Content-Type", "text/plain; version=0.0.4; charset=utf-8")
var b strings.Builder
fmt.Fprintf(&b, "# HELP wgx_peers Number of configured peers.\n# TYPE wgx_peers gauge\nwgx_peers %d\n", snap.Totals.Peers)
fmt.Fprintf(&b, "# HELP wgx_peers_connected Peers with a recent handshake.\n# TYPE wgx_peers_connected gauge\nwgx_peers_connected %d\n", snap.Totals.Connected)
fmt.Fprintf(&b, "# HELP wgx_receive_bytes_total Bytes received from peers.\n# TYPE wgx_receive_bytes_total counter\n")
fmt.Fprintf(&b, "# HELP ihasvpn_peers Number of configured peers.\n# TYPE ihasvpn_peers gauge\nihasvpn_peers %d\n", snap.Totals.Peers)
fmt.Fprintf(&b, "# HELP ihasvpn_peers_connected Peers with a recent handshake.\n# TYPE ihasvpn_peers_connected gauge\nihasvpn_peers_connected %d\n", snap.Totals.Connected)
fmt.Fprintf(&b, "# HELP ihasvpn_receive_bytes_total Bytes received from peers.\n# TYPE ihasvpn_receive_bytes_total counter\n")
for id, l := range snap.Peers {
fmt.Fprintf(&b, "wgx_receive_bytes_total{peer=%q,name=%q} %d\n", id, names[id], l.Rx)
fmt.Fprintf(&b, "ihasvpn_receive_bytes_total{peer=%q,name=%q} %d\n", id, names[id], l.Rx)
}
fmt.Fprintf(&b, "# HELP wgx_transmit_bytes_total Bytes sent to peers.\n# TYPE wgx_transmit_bytes_total counter\n")
fmt.Fprintf(&b, "# HELP ihasvpn_transmit_bytes_total Bytes sent to peers.\n# TYPE ihasvpn_transmit_bytes_total counter\n")
for id, l := range snap.Peers {
fmt.Fprintf(&b, "wgx_transmit_bytes_total{peer=%q,name=%q} %d\n", id, names[id], l.Tx)
fmt.Fprintf(&b, "ihasvpn_transmit_bytes_total{peer=%q,name=%q} %d\n", id, names[id], l.Tx)
}
fmt.Fprintf(&b, "# HELP wgx_peer_connected Whether the peer has a recent handshake.\n# TYPE wgx_peer_connected gauge\n")
fmt.Fprintf(&b, "# HELP ihasvpn_peer_connected Whether the peer has a recent handshake.\n# TYPE ihasvpn_peer_connected gauge\n")
for id, l := range snap.Peers {
v := 0
if l.Connected {
v = 1
}
fmt.Fprintf(&b, "wgx_peer_connected{peer=%q,name=%q} %d\n", id, names[id], v)
fmt.Fprintf(&b, "ihasvpn_peer_connected{peer=%q,name=%q} %d\n", id, names[id], v)
}
fmt.Fprintf(&b, "# HELP wgx_peer_last_handshake_seconds Unix time of the last handshake.\n# TYPE wgx_peer_last_handshake_seconds gauge\n")
fmt.Fprintf(&b, "# HELP ihasvpn_peer_last_handshake_seconds Unix time of the last handshake.\n# TYPE ihasvpn_peer_last_handshake_seconds gauge\n")
for id, l := range snap.Peers {
if !l.LastHandshake.IsZero() {
fmt.Fprintf(&b, "wgx_peer_last_handshake_seconds{peer=%q,name=%q} %d\n", id, names[id], l.LastHandshake.Unix())
fmt.Fprintf(&b, "ihasvpn_peer_last_handshake_seconds{peer=%q,name=%q} %d\n", id, names[id], l.LastHandshake.Unix())
}
}
_, _ = w.Write([]byte(b.String()))
+5 -5
View File
@@ -10,11 +10,11 @@ import (
"github.com/skip2/go-qrcode"
"github.com/Coffey-Labs/WGX/internal/auth"
"github.com/Coffey-Labs/WGX/internal/store"
"github.com/Coffey-Labs/ihasvpn/internal/auth"
"github.com/Coffey-Labs/ihasvpn/internal/store"
)
const cookieName = "wgx_session"
const cookieName = "ihasvpn_session"
type ctxKey int
@@ -450,7 +450,7 @@ func (s *Server) handleTOTPSetup(w http.ResponseWriter, r *http.Request) {
writeError(w, http.StatusInternalServerError, err.Error())
return
}
writeJSON(w, http.StatusOK, totpSetupResponse{Secret: secret, URI: auth.TOTPURI("WGX", u.Username, secret)})
writeJSON(w, http.StatusOK, totpSetupResponse{Secret: secret, URI: auth.TOTPURI("ihasvpn", u.Username, secret)})
}
// handleTOTPQR renders the pending secret's otpauth URI as a QR code. Only a
@@ -462,7 +462,7 @@ func (s *Server) handleTOTPQR(w http.ResponseWriter, r *http.Request) {
writeError(w, http.StatusNotFound, "no two-factor setup in progress")
return
}
png, err := qrcode.Encode(auth.TOTPURI("WGX", u.Username, u.TOTPSecret), qrcode.Medium, 256)
png, err := qrcode.Encode(auth.TOTPURI("ihasvpn", u.Username, u.TOTPSecret), qrcode.Medium, 256)
if err != nil {
writeError(w, http.StatusInternalServerError, err.Error())
return
+4 -4
View File
@@ -17,10 +17,10 @@ import (
"strings"
"time"
"github.com/Coffey-Labs/WGX/internal/auth"
"github.com/Coffey-Labs/WGX/internal/config"
"github.com/Coffey-Labs/WGX/internal/engine"
"github.com/Coffey-Labs/WGX/internal/server/static"
"github.com/Coffey-Labs/ihasvpn/internal/auth"
"github.com/Coffey-Labs/ihasvpn/internal/config"
"github.com/Coffey-Labs/ihasvpn/internal/engine"
"github.com/Coffey-Labs/ihasvpn/internal/server/static"
)
// Server serves the API and UI.
+6 -6
View File
@@ -14,11 +14,11 @@ import (
"testing"
"time"
"github.com/Coffey-Labs/WGX/internal/auth"
"github.com/Coffey-Labs/WGX/internal/config"
"github.com/Coffey-Labs/WGX/internal/engine"
"github.com/Coffey-Labs/WGX/internal/store"
"github.com/Coffey-Labs/WGX/internal/wg"
"github.com/Coffey-Labs/ihasvpn/internal/auth"
"github.com/Coffey-Labs/ihasvpn/internal/config"
"github.com/Coffey-Labs/ihasvpn/internal/engine"
"github.com/Coffey-Labs/ihasvpn/internal/store"
"github.com/Coffey-Labs/ihasvpn/internal/wg"
)
type client struct {
@@ -178,7 +178,7 @@ func TestSetupLoginAndPeers(t *testing.T) {
req.Header.Set("Authorization", "Bearer metrics-secret")
r, _ := anon.Do(req)
b, _ := io.ReadAll(r.Body)
if r.StatusCode != 200 || !strings.Contains(string(b), "wgx_peers ") {
if r.StatusCode != 200 || !strings.Contains(string(b), "ihasvpn_peers ") {
t.Fatalf("token metrics: %d %s", r.StatusCode, b)
}
}
+1 -1
View File
@@ -45,7 +45,7 @@ func (s *Server) loadCertificate() (tls.Certificate, error) {
host := s.eng.Settings().EndpointHost
tmpl := &x509.Certificate{
SerialNumber: serial,
Subject: pkix.Name{CommonName: "WGX", Organization: []string{"WGX"}},
Subject: pkix.Name{CommonName: "ihasvpn", Organization: []string{"ihasvpn"}},
NotBefore: time.Now().Add(-time.Hour),
NotAfter: time.Now().Add(3 * 365 * 24 * time.Hour),
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,