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
+35 -35
View File
@@ -104,52 +104,52 @@ func envDuration(key string, def time.Duration) (time.Duration, error) {
return d, nil
}
// FromEnv builds the configuration from WGX_* variables.
// FromEnv builds the configuration from IHASVPN_* variables.
func FromEnv() (*Config, error) {
var errs []error
c := &Config{}
c.DataDir = env("WGX_DATA_DIR", "/data")
c.DBPath = env("WGX_DB", c.DataDir+"/wgx.db")
c.Backend = strings.ToLower(env("WGX_BACKEND", "auto"))
c.DataDir = env("IHASVPN_DATA_DIR", "/data")
c.DBPath = env("IHASVPN_DB", c.DataDir+"/ihasvpn.db")
c.Backend = strings.ToLower(env("IHASVPN_BACKEND", "auto"))
switch c.Backend {
case "auto", "kernel", "userspace", "mock":
default:
errs = append(errs, fmt.Errorf("WGX_BACKEND: %q is not auto, kernel, userspace or mock", c.Backend))
errs = append(errs, fmt.Errorf("IHASVPN_BACKEND: %q is not auto, kernel, userspace or mock", c.Backend))
}
c.Iface = env("WGX_INTERFACE", "wg0")
c.Iface = env("IHASVPN_INTERFACE", "wg0")
if len(c.Iface) == 0 || len(c.Iface) > 15 || strings.ContainsAny(c.Iface, " /\t\n") {
errs = append(errs, errors.New("WGX_INTERFACE: must be 1-15 characters with no spaces or slashes"))
errs = append(errs, errors.New("IHASVPN_INTERFACE: must be 1-15 characters with no spaces or slashes"))
}
var err error
if c.ListenPort, err = envInt("WGX_PORT", 51820); err != nil {
if c.ListenPort, err = envInt("IHASVPN_PORT", 51820); err != nil {
errs = append(errs, err)
} else if c.ListenPort < 1 || c.ListenPort > 65535 {
errs = append(errs, errors.New("WGX_PORT: must be 1-65535"))
errs = append(errs, errors.New("IHASVPN_PORT: must be 1-65535"))
}
if c.Subnet4, err = netip.ParsePrefix(env("WGX_SUBNET", "10.8.0.0/24")); err != nil || !c.Subnet4.Addr().Is4() {
errs = append(errs, errors.New("WGX_SUBNET: must be an IPv4 CIDR such as 10.8.0.0/24"))
if c.Subnet4, err = netip.ParsePrefix(env("IHASVPN_SUBNET", "10.8.0.0/24")); err != nil || !c.Subnet4.Addr().Is4() {
errs = append(errs, errors.New("IHASVPN_SUBNET: must be an IPv4 CIDR such as 10.8.0.0/24"))
} else if c.Subnet4.Bits() > 30 {
errs = append(errs, errors.New("WGX_SUBNET: needs room for at least two hosts (/30 or larger)"))
errs = append(errs, errors.New("IHASVPN_SUBNET: needs room for at least two hosts (/30 or larger)"))
}
if v := env("WGX_SUBNET6", ""); v != "" {
if v := env("IHASVPN_SUBNET6", ""); v != "" {
if c.Subnet6, err = netip.ParsePrefix(v); err != nil || !c.Subnet6.Addr().Is6() {
errs = append(errs, errors.New("WGX_SUBNET6: must be an IPv6 CIDR such as fd42:42:42::/64"))
errs = append(errs, errors.New("IHASVPN_SUBNET6: must be an IPv6 CIDR such as fd42:42:42::/64"))
}
}
c.Egress = env("WGX_EGRESS_INTERFACE", "")
c.HTTP = env("WGX_HTTP_LISTEN", ":51821")
c.TLSCert = env("WGX_TLS_CERT", "")
c.TLSKey = env("WGX_TLS_KEY", "")
c.Egress = env("IHASVPN_EGRESS_INTERFACE", "")
c.HTTP = env("IHASVPN_HTTP_LISTEN", ":51821")
c.TLSCert = env("IHASVPN_TLS_CERT", "")
c.TLSKey = env("IHASVPN_TLS_KEY", "")
if (c.TLSCert == "") != (c.TLSKey == "") {
errs = append(errs, errors.New("WGX_TLS_CERT and WGX_TLS_KEY must be set together"))
errs = append(errs, errors.New("IHASVPN_TLS_CERT and IHASVPN_TLS_KEY must be set together"))
}
if c.TLSSelfSigned, err = envBool("WGX_TLS_SELF_SIGNED", false); err != nil {
if c.TLSSelfSigned, err = envBool("IHASVPN_TLS_SELF_SIGNED", false); err != nil {
errs = append(errs, err)
}
if c.SecureCookies, err = envBool("WGX_SECURE_COOKIES", false); err != nil {
if c.SecureCookies, err = envBool("IHASVPN_SECURE_COOKIES", false); err != nil {
errs = append(errs, err)
}
for _, p := range strings.Split(env("WGX_TRUSTED_PROXIES", ""), ",") {
for _, p := range strings.Split(env("IHASVPN_TRUSTED_PROXIES", ""), ",") {
p = strings.TrimSpace(p)
if p == "" {
continue
@@ -159,37 +159,37 @@ func FromEnv() (*Config, error) {
if a, err2 := netip.ParseAddr(p); err2 == nil {
pfx = netip.PrefixFrom(a, a.BitLen())
} else {
errs = append(errs, fmt.Errorf("WGX_TRUSTED_PROXIES: %q is not an address or CIDR", p))
errs = append(errs, fmt.Errorf("IHASVPN_TRUSTED_PROXIES: %q is not an address or CIDR", p))
continue
}
}
c.TrustedProxies = append(c.TrustedProxies, pfx)
}
c.MetricsToken = env("WGX_METRICS_TOKEN", "")
if c.SessionIdle, err = envDuration("WGX_SESSION_IDLE", 12*time.Hour); err != nil {
c.MetricsToken = env("IHASVPN_METRICS_TOKEN", "")
if c.SessionIdle, err = envDuration("IHASVPN_SESSION_IDLE", 12*time.Hour); err != nil {
errs = append(errs, err)
}
if c.SessionMax, err = envDuration("WGX_SESSION_MAX", 7*24*time.Hour); err != nil {
if c.SessionMax, err = envDuration("IHASVPN_SESSION_MAX", 7*24*time.Hour); err != nil {
errs = append(errs, err)
}
if c.TrafficRetention, err = envDuration("WGX_TRAFFIC_RETENTION", 90*24*time.Hour); err != nil {
if c.TrafficRetention, err = envDuration("IHASVPN_TRAFFIC_RETENTION", 90*24*time.Hour); err != nil {
errs = append(errs, err)
}
if c.PollInterval, err = envDuration("WGX_POLL_INTERVAL", 2*time.Second); err != nil {
if c.PollInterval, err = envDuration("IHASVPN_POLL_INTERVAL", 2*time.Second); err != nil {
errs = append(errs, err)
} else if c.PollInterval < 500*time.Millisecond {
errs = append(errs, errors.New("WGX_POLL_INTERVAL: must be at least 500ms"))
errs = append(errs, errors.New("IHASVPN_POLL_INTERVAL: must be at least 500ms"))
}
c.LogLevel = strings.ToLower(env("WGX_LOG_LEVEL", "info"))
if c.LogJSON, err = envBool("WGX_LOG_JSON", false); err != nil {
c.LogLevel = strings.ToLower(env("IHASVPN_LOG_LEVEL", "info"))
if c.LogJSON, err = envBool("IHASVPN_LOG_JSON", false); err != nil {
errs = append(errs, err)
}
c.InitialEndpoint = env("WGX_ENDPOINT", "")
c.InitialDNS = env("WGX_DNS", "1.1.1.1, 1.0.0.1")
if c.ManageFirewall, err = envBool("WGX_MANAGE_FIREWALL", true); err != nil {
c.InitialEndpoint = env("IHASVPN_ENDPOINT", "")
c.InitialDNS = env("IHASVPN_DNS", "1.1.1.1, 1.0.0.1")
if c.ManageFirewall, err = envBool("IHASVPN_MANAGE_FIREWALL", true); err != nil {
errs = append(errs, err)
}
if c.ManageSysctl, err = envBool("WGX_MANAGE_SYSCTL", true); err != nil {
if c.ManageSysctl, err = envBool("IHASVPN_MANAGE_SYSCTL", true); err != nil {
errs = append(errs, err)
}
if len(errs) > 0 {