commit f9529c605515224be0a79c453f5aadebd368ebd4 Author: sapient Date: Sun Mar 22 00:54:58 2026 -0700 chore: initial commit of wgtool diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a54a397 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.env +.env.* +*.log +node_modules/ +.venv/ diff --git a/CURRENT_WORKING/aza.conf b/CURRENT_WORKING/aza.conf new file mode 100644 index 0000000..1ea102a --- /dev/null +++ b/CURRENT_WORKING/aza.conf @@ -0,0 +1,10 @@ +[Interface] +PrivateKey = UI7XeN/0WGCziv68Vt3hlPGYWB1dwZN7+N2C4Y2y91o= +Address = 10.8.0.2/24 + +# Zion (central server) +[Peer] +PublicKey = 2ztJbrN1x1NWanzPGLiKL19ZkdOhm5Y7WeKEWBT5cyg= +AllowedIPs = 10.8.0.0/24 +Endpoint = ugh.im:51820 +PersistentKeepalive = 60 diff --git a/CURRENT_WORKING/cth.conf b/CURRENT_WORKING/cth.conf new file mode 100644 index 0000000..a78c5d9 --- /dev/null +++ b/CURRENT_WORKING/cth.conf @@ -0,0 +1,81 @@ +[Interface] +Address = 10.8.0.10/24 +ListenPort = 53535 +PrivateKey = UDr1aohdSWGMEy1F6v0GoC7JNgVHbwA8Dz//7E2xQHM= + +#pix66 +[Peer] +PublicKey = hguz42G5S8EV3NmkORc6eiBWb+V9Z6oBdiXVnAcqvmI= +AllowedIPs = 10.8.0.6/32 + + + + + + + +#Cth +#[Peer] +#PublicKey = NBktXKy1s0n2lIlIMODvOqKNwAtYdoZH5feKt5P43i0= +#AllowedIPs = 10.8.0.10/32 + +#Aza +#[Peer] +#PublicKey = qmTKA257DLOrfhk5Zw8RyRmBSonmm6epbloT0P0ZWDc= +#AllowedIPs = 10.8.0.2/32 + +#Nyar +[Peer] +PublicKey = 2BA7L1oJP1tK6dIUNHMgcZmOmYmlyPRe2RaBqfUsEWo= +AllowedIPs = 10.8.0.20/32 + +#Galaxy +#[Peer] +#PublicKey = QBNt00VSedxPlq3ZvsdYaqIcbudCAyxv9TG65aPVZzM= +#AllowedIPs = 10.8.0.99/32 + +#nanocube +#[Peer] +#PublicKey = /ZImoATDIS0e0N08CD7mqWbhtGlSnynpPuY04Ed4Zyc= +#AllowedIPs = 10.8.0.7/32 + +#jupiter +#[Peer] +#PublicKey = YIFQ43ULk/YoCgOv3SBU6+MOrbxd+mlvaw9rT8uoNmw= +#AllowedIPs = 10.8.0.42/32 + +#HASS +#[Peer] +#PublicKey = C+Poz/7DaXCxe4HZiL6D5cld4jMt5o1gBq3iPiBzrg0= +#AllowedIPs = 10.8.0.8/32 + +#framebot +#[Peer] +#PublicKey = loS3yZapqmt6lP53Q+s4EvUzw6FmwgZC8jzgLluJ1Es= +#AllowedIPs = 10.8.0.40/32 + + +[Peer] +PublicKey = 2ztJbrN1x1NWanzPGLiKL19ZkdOhm5Y7WeKEWBT5cyg= +AllowedIPs = 10.8.0.0/24 +Endpoint = ugh.im:51820 +PersistentKeepalive = 25 + + +#[Peer] +#PublicKey = 2BA7L1oJP1tK6dIUNHMgcZmOmYmlyPRe2RaBqfUsEWo= +#AllowedIPs = 10.8.0.20/32 +#PersistentKeepalive = 25 + +#jupiter +#[Peer] +#PublicKey = YIFQ43ULk/YoCgOv3SBU6+MOrbxd+mlvaw9rT8uoNmw= +#AllowedIPs = 10.8.0.42/32 + + +#Galaxy +#[Peer] +#PublicKey = QBNt00VSedxPlq3ZvsdYaqIcbudCAyxv9TG65aPVZzM= +#AllowedIPs = 10.8.0.99/32 +#Endpoint = galaxyspin.space:54382 +#PersistentKeepalive = 25 diff --git a/CURRENT_WORKING/galaxy.conf b/CURRENT_WORKING/galaxy.conf new file mode 100644 index 0000000..2d46d6b --- /dev/null +++ b/CURRENT_WORKING/galaxy.conf @@ -0,0 +1,16 @@ +[Interface] +PrivateKey = sHEHZQ3AdUTLG56yeHdmZVuXrivB9P+40Fw0oWHF3Fg= +Address = 10.8.0.99/24 +ListenPort = 54382 + +# Zion peer (central server) - for access to entire network +[Peer] +PublicKey = 2ztJbrN1x1NWanzPGLiKL19ZkdOhm5Y7WeKEWBT5cyg= +AllowedIPs = 10.8.0.0/24 +Endpoint = ugh.im:51820 +PersistentKeepalive = 25 + + + + + diff --git a/CURRENT_WORKING/nyan.conf b/CURRENT_WORKING/nyan.conf new file mode 100644 index 0000000..7cfc6e9 --- /dev/null +++ b/CURRENT_WORKING/nyan.conf @@ -0,0 +1,25 @@ +[Interface] +PrivateKey = sLx3sor9gxhk7T2MIS3g50wzSZBrlVUG+NlXk47jEH4= +Address = 10.8.0.20/24 + +# Zion peer (central server) - for access to entire network +[Peer] +PublicKey = 2ztJbrN1x1NWanzPGLiKL19ZkdOhm5Y7WeKEWBT5cyg= +AllowedIPs = 10.8.0.0/24 +Endpoint = ugh.im:51820 +PersistentKeepalive = 25 + +#CTH +[Peer] +PublicKey = NBktXKy1s0n2lIlIMODvOqKNwAtYdoZH5feKt5P43i0= +AllowedIPs = 10.8.0.10/32 +Endpoint = aw2cd67.glddns.com:53535 +PersistentKeepalive = 25 + + +#Galaxy +#[Peer] +#PublicKey = QBNt00VSedxPlq3ZvsdYaqIcbudCAyxv9TG65aPVZzM= +#AllowedIPs = 10.8.0.99/32 +#Endpoint = galaxyspin.space:54382 +#PersistentKeepalive = 25 diff --git a/CURRENT_WORKING/zion.conf b/CURRENT_WORKING/zion.conf new file mode 100644 index 0000000..534a211 --- /dev/null +++ b/CURRENT_WORKING/zion.conf @@ -0,0 +1,48 @@ +[Interface] +Address = 10.8.0.1/24 +ListenPort = 51820 +PrivateKey = UJvsfv6iQPAW9Wnc81bK0o3IIHX86kGb+24dUTuGFnA= +PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE; ip route add 10.8.0.0/24 dev wg0 2>/dev/null || true +PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE; ip route del 10.8.0.0/24 dev wg0 2>/dev/null || true + +#Cth +[Peer] +PublicKey = NBktXKy1s0n2lIlIMODvOqKNwAtYdoZH5feKt5P43i0= +AllowedIPs = 10.8.0.10/32 + +#Aza +[Peer] +PublicKey = qmTKA257DLOrfhk5Zw8RyRmBSonmm6epbloT0P0ZWDc= +AllowedIPs = 10.8.0.2/32 + +#Nyar +[Peer] +PublicKey = 2BA7L1oJP1tK6dIUNHMgcZmOmYmlyPRe2RaBqfUsEWo= +AllowedIPs = 10.8.0.20/32 + +#Galaxy +[Peer] +PublicKey = QBNt00VSedxPlq3ZvsdYaqIcbudCAyxv9TG65aPVZzM= +AllowedIPs = 10.8.0.99/32 + + + +#nanocube +[Peer] +PublicKey = /ZImoATDIS0e0N08CD7mqWbhtGlSnynpPuY04Ed4Zyc= +AllowedIPs = 10.8.0.7/32 + +#jupiter +[Peer] +PublicKey = YIFQ43ULk/YoCgOv3SBU6+MOrbxd+mlvaw9rT8uoNmw= +AllowedIPs = 10.8.0.42/32 + +#HASS +[Peer] +PublicKey = C+Poz/7DaXCxe4HZiL6D5cld4jMt5o1gBq3iPiBzrg0= +AllowedIPs = 10.8.0.8/32 + +#framebot +[Peer] +PublicKey = loS3yZapqmt6lP53Q+s4EvUzw6FmwgZC8jzgLluJ1Es= +AllowedIPs = 10.8.0.40/32 diff --git a/GO_VS_BASH_ANALYSIS.md b/GO_VS_BASH_ANALYSIS.md new file mode 100644 index 0000000..20ee8f9 --- /dev/null +++ b/GO_VS_BASH_ANALYSIS.md @@ -0,0 +1,360 @@ +# Go vs Bash Implementation Analysis + +## Overview + +This document compares the bash implementation (`wireguard_setup.sh`) with the Go implementation (`wireguard_setup.go`) and identifies key improvements and advantages of the Go version. + +## Key Improvements in Go Version + +### 1. **Type Safety and Error Handling** + +**Bash Version:** +```bash +# Basic string validation +if [ -z "$NODE_NAME" ]; then + print_error "Node name cannot be empty" + exit 1 +fi +``` + +**Go Version:** +```go +// Strong typing with structured validation +func validateHostname(hostname string) error { + if hostname == "" { + return fmt.Errorf("hostname cannot be empty") + } + if len(hostname) > 63 { + return fmt.Errorf("hostname too long (max 63 characters)") + } + hostnameRegex := regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]$|^[a-zA-Z0-9]$`) + if !hostnameRegex.MatchString(hostname) { + return fmt.Errorf("invalid hostname format. Use alphanumeric characters and hyphens only") + } + return nil +} +``` + +**Advantages:** +- Compile-time error checking +- Structured error handling with context +- Type safety prevents runtime errors +- Better validation with regex patterns + +### 2. **Configuration Management** + +**Bash Version:** +```bash +# Hardcoded values scattered throughout +ZION_PUBLIC_KEY="2ztJbrN1x1NWanzPGLiKL19ZkdOhm5Y7WeKEWBT5cyg=" +ZION_ENDPOINT="ugh.im:51820" +ZION_ALLOWED_IPS="10.8.0.0/24" +``` + +**Go Version:** +```go +// Structured configuration with type safety +type ZionConfig struct { + PublicKey string `json:"public_key"` + Endpoint string `json:"endpoint"` + Peers map[string]string `json:"peers"` // name -> public_key +} + +var zionConfig = ZionConfig{ + PublicKey: "2ztJbrN1x1NWanzPGLiKL19ZkdOhm5Y7WeKEWBT5cyg=", + Endpoint: "ugh.im:51820", + Peers: map[string]string{ + "Cth": "NBktXKy1s0n2lIlIMODvOqKNwAtYdoZH5feKt5P43i0=", + "Aza": "qmTKA257DLOrfhk5Zw8RyRmBSonmm6epbloT0P0ZWDc=", + // ... more peers + }, +} +``` + +**Advantages:** +- Centralized configuration management +- JSON serialization support +- Type-safe access to configuration +- Easy to extend and maintain + +### 3. **IP Address Validation** + +**Bash Version:** +```bash +# Basic regex validation +validate_ip() { + local ip=$1 + if [[ $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/[0-9]{1,2}$ ]]; then + return 0 + else + return 1 + fi +} +``` + +**Go Version:** +```go +func validateIP(ip string) error { + if ip == "" { + return fmt.Errorf("IP address cannot be empty") + } + + // Check if it's in the expected range + if !ipRegex.MatchString(ip) { + return fmt.Errorf("IP should be in 10.8.0.x range for NextGen network") + } + + // Validate IP format using net package + parsedIP := net.ParseIP(ip) + if parsedIP == nil { + return fmt.Errorf("invalid IP address format") + } + + // Check for conflicts with existing peers + for peerName, peerIP := range map[string]string{ + "Zion": "10.8.0.1", + "Cth": "10.8.0.10", + // ... more peers + } { + if ip == peerIP { + return fmt.Errorf("IP %s is already in use by %s", ip, peerName) + } + } + + return nil +} +``` + +**Advantages:** +- Uses Go's `net` package for proper IP validation +- Checks for IP conflicts with existing peers +- More comprehensive validation +- Better error messages + +### 4. **Key Generation** + +**Bash Version:** +```bash +# Relies on external wg command +PRIVATE_KEY=$(wg genkey) +PUBLIC_KEY=$(echo "$PRIVATE_KEY" | wg pubkey) +``` + +**Go Version:** +```go +func generateWireGuardKeys() (string, string, error) { + // Generate private key + privateKeyBytes := make([]byte, 32) + if _, err := rand.Read(privateKeyBytes); err != nil { + return "", "", err + } + + // Ensure the key is valid for curve25519 + privateKeyBytes[0] &= 248 + privateKeyBytes[31] &= 127 + privateKeyBytes[31] |= 64 + + // Generate public key using curve25519 + var publicKeyBytes [32]byte + curve25519.ScalarBaseMult(&publicKeyBytes, (*[32]byte)(privateKeyBytes)) + + privateKey := base64.StdEncoding.EncodeToString(privateKeyBytes[:]) + publicKey := base64.StdEncoding.EncodeToString(publicKeyBytes[:]) + + return privateKey, publicKey, nil +} +``` + +**Advantages:** +- No external dependencies for key generation +- Uses cryptographically secure random number generator +- Proper curve25519 key generation +- Better error handling + +### 5. **File Operations** + +**Bash Version:** +```bash +# Basic file operations +cat > "$CONFIG_FILE" << EOF +[Interface] +PrivateKey = $PRIVATE_KEY +Address = $IP_ADDRESS +EOF +``` + +**Go Version:** +```go +// Structured file operations with error handling +func generateConfig(hostname, ipAddress, privateKey, routingMode string) string { + var config strings.Builder + + // Interface section + config.WriteString("[Interface]\n") + config.WriteString(fmt.Sprintf("Address = %s/24\n", ipAddress)) + config.WriteString(fmt.Sprintf("PrivateKey = %s\n", privateKey)) + + // Add ListenPort for static servers + if port, exists := staticServers[ipAddress]; exists { + config.WriteString(fmt.Sprintf("ListenPort = %s\n", port)) + } + + // Add DNS for full tunnel mode + if routingMode == "full_tunnel" { + config.WriteString("DNS = 1.1.1.1, 8.8.8.8\n") + } + + // Zion peer (always enabled) + config.WriteString("\n# Zion (central server)\n") + config.WriteString("[Peer]\n") + config.WriteString(fmt.Sprintf("PublicKey = %s\n", zionConfig.PublicKey)) + // ... more configuration + + return config.String() +} +``` + +**Advantages:** +- Uses `strings.Builder` for efficient string concatenation +- Structured configuration generation +- Better memory efficiency +- Type-safe operations + +### 6. **User Input Handling** + +**Bash Version:** +```bash +# Basic input with limited validation +read -p "Enter node name (e.g., aza, cth, galaxy): " NODE_NAME +if [ -z "$NODE_NAME" ]; then + print_error "Node name cannot be empty" + exit 1 +fi +``` + +**Go Version:** +```go +func getUserInput(prompt string, validator func(string) error) string { + reader := bufio.NewReader(os.Stdin) + for { + fmt.Print(prompt) + input, _ := reader.ReadString('\n') + input = strings.TrimSpace(input) + + if err := validator(input); err != nil { + printError(err.Error()) + fmt.Print("Continue anyway? (y/N): ") + response, _ := reader.ReadString('\n') + response = strings.TrimSpace(strings.ToLower(response)) + if response == "y" || response == "yes" { + return input + } + continue + } + return input + } +} +``` + +**Advantages:** +- Function-based validation +- Better error recovery +- Consistent input handling +- More flexible validation logic + +### 7. **JSON Configuration Export** + +**Go Version Only:** +```go +type WGConfig struct { + Hostname string `json:"hostname"` + IPAddress string `json:"ip_address"` + PrivateKey string `json:"private_key"` + PublicKey string `json:"public_key"` + RoutingMode string `json:"routing_mode"` + Generated string `json:"generated"` + ScriptVer string `json:"script_version"` + RunningRoot bool `json:"running_as_root"` + ZionPeer bool `json:"zion_peer_added"` +} + +// Automatic JSON serialization +infoData, err := json.MarshalIndent(wgConfig, "", " ") +if err != nil { + printError(fmt.Sprintf("Failed to marshal config: %v", err)) + os.Exit(1) +} +``` + +**Advantages:** +- Structured data export +- Easy integration with other tools +- Machine-readable configuration +- Better debugging and logging + +## Performance Comparison + +### Memory Usage +- **Bash**: Higher memory usage due to string operations and external commands +- **Go**: More efficient memory usage with `strings.Builder` and structured data + +### Execution Speed +- **Bash**: Slower due to external command calls (`wg genkey`, `wg pubkey`) +- **Go**: Faster with native key generation and optimized string operations + +### Dependencies +- **Bash**: Requires external tools (`wg`, `wg-quick`, `bash`) +- **Go**: Single binary with minimal runtime dependencies + +## Maintainability + +### Code Organization +- **Bash**: Functions scattered throughout, harder to maintain +- **Go**: Structured with clear separation of concerns + +### Error Handling +- **Bash**: Basic error checking with exit codes +- **Go**: Comprehensive error handling with context + +### Testing +- **Bash**: Difficult to unit test +- **Go**: Easy to unit test with structured functions + +## Security Improvements + +### Key Generation +- **Bash**: Relies on external `wg` command +- **Go**: Uses cryptographically secure random number generator + +### Input Validation +- **Bash**: Basic regex validation +- **Go**: Comprehensive validation with multiple checks + +### File Operations +- **Bash**: Basic file operations +- **Go**: Proper error handling and atomic operations + +## Cross-Platform Compatibility + +### Distribution +- **Bash**: Requires bash shell and external tools +- **Go**: Single binary that works on any platform + +### Dependencies +- **Bash**: Platform-specific package managers +- **Go**: No external dependencies + +## Conclusion + +The Go implementation provides significant improvements over the bash version: + +1. **Better Type Safety**: Compile-time error checking prevents runtime issues +2. **Improved Error Handling**: Structured error handling with context +3. **Enhanced Validation**: More comprehensive input validation +4. **Better Performance**: Native key generation and optimized operations +5. **Easier Maintenance**: Structured code with clear separation of concerns +6. **Cross-Platform**: Single binary with no external dependencies +7. **Better Security**: Cryptographically secure operations +8. **Structured Output**: JSON configuration export for integration + +The Go version is more suitable for production use, especially in environments where reliability, security, and maintainability are important. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..d2d2eec --- /dev/null +++ b/README.md @@ -0,0 +1,121 @@ +## wgtool (WireGuard helper CLI) + +wgtool streamlines creating and validating WireGuard configs, and generating a ready-to-paste Zion peer block. + +### Features +- Generate WireGuard configs with sensible defaults +- Validate single configs or all .conf files in a directory +- Print a Zion-ready [Peer] snippet for adding new nodes +- Generate private keys (derive public key with `wg pubkey`) + +### Commands +- `generate`: Create a config and write keys +- `validate`: Lint a config file or all `.conf` files in a directory +- `zion-peer`: Print a `[Peer]` block for Zion’s `wg0.conf` +- `keys`: Print a private key +- `version`: Show tool version + +### Defaults and endpoints +- Adds one default peer in generated configs: + - Zion (central server) + - PublicKey: `2ztJbrN1x1NWanzPGLiKL19ZkdOhm5Y7WeKEWBT5cyg=` + - Endpoint: `ugh.im:51820` + - AllowedIPs: + - `wg_only`: `10.8.0.0/24` + - `full_tunnel`: `0.0.0.0/0, ::/0` + - PersistentKeepalive: `25` + +### generate +Create a config into `wireguard_configs/` and output keys alongside it. Missing flags are prompted interactively unless `--yes` is used. + +Flags: +- `--hostname` Node name (e.g., `aza`) +- `--ip` Node IP in `10.8.0.x` +- `--interface` Interface name (default `wg0`) +- `--routing` `wg_only` | `full_tunnel` (default `wg_only`) +- `--out` Output directory (default `wireguard_configs`) +- `--force` Overwrite without prompt +- `--yes` Non-interactive (assume yes) + +Examples: +```bash +./wgtool generate +./wgtool generate --hostname aza --ip 10.8.0.30 --interface wg0 --routing wg_only --out wireguard_configs --yes +./wgtool generate --hostname aza --ip 10.8.0.30 --routing full_tunnel --yes +``` + +Outputs: +- `wireguard_configs/wg0.conf` +- `wireguard_configs/_private.key` +- Derive public key: `echo "" | wg pubkey` + +### validate +Validate a config file or every `.conf` in a directory. + +Flags: +- `--target` Path to a file or directory + +Examples: +```bash +./wgtool validate --target wireguard_configs/wg0.conf +./wgtool validate --target wireguard_configs +``` + +Checks include: +- Presence of `[Interface]` +- `PrivateKey` and CIDR `Address` +- Peer `PublicKey` format +- `AllowedIPs` as valid CIDRs + +### zion-peer +Print a `[Peer]` block to add into Zion’s `/etc/wireguard/wg0.conf` for a new node. + +Flags: +- `--name` Node name +- `--pub` Node public key (44-char base64 ending with `=`) +- `--ip` Node IP in `10.8.0.x` (host address) + +Example: +```bash +./wgtool zion-peer --name aza --pub ABCDEFG...= --ip 10.8.0.30 +``` + +### keys +Generate and print a private key. + +Example: +```bash +./wgtool keys +# derive pub +echo "" | wg pubkey +``` + +### Quick start +1) Create a config and keys +```bash +./wgtool generate --hostname mynode --ip 10.8.0.30 --yes +``` + +2) Validate the config +```bash +./wgtool validate --target wireguard_configs/wg0.conf +``` + +3) Give Zion your peer details +```bash +./wgtool zion-peer --name mynode --pub $(echo "" | wg pubkey) --ip 10.8.0.30 +``` + +4) Install and enable (on your node) +```bash +sudo cp wireguard_configs/wg0.conf /etc/wireguard/ +sudo chmod 600 /etc/wireguard/wg0.conf +sudo systemctl enable --now wg-quick@wg0 +``` + +Notes: +- IPs are enforced in the `10.8.0.x` range. +- In `full_tunnel` mode DNS is set to `1.1.1.1, 8.8.8.8`. +- Overwrites are blocked unless `--force` or confirmed interactively. + + diff --git a/SCRIPT_IMPROVEMENTS.md b/SCRIPT_IMPROVEMENTS.md new file mode 100644 index 0000000..5a56632 --- /dev/null +++ b/SCRIPT_IMPROVEMENTS.md @@ -0,0 +1,138 @@ +# WireGuard Script Improvements Summary + +This document outlines the errors found and optimizations made to the WireGuard setup scripts. + +## Scripts Analyzed + +1. `generate_zion_peer.sh` - Zion peer configuration generator +2. `wireguard_setup.sh` - Interactive WireGuard setup script +3. `wireguard_setup.go` - Go-based WireGuard setup tool + +## Issues Found and Fixed + +### 1. generate_zion_peer.sh + +#### Issues Fixed: +- **Shebang**: Changed from `/bin/bash` to `/usr/bin/env bash` for better portability +- **Error handling**: Added `set -euo pipefail` for stricter error handling +- **IP validation**: Improved regex to properly validate 10.8.0.x format and exclude reserved addresses +- **Public key validation**: Enhanced validation for WireGuard public keys (44 character base64) +- **Input sanitization**: Added validation for node names +- **Configuration loading**: Added ability to load Zion config from file with fallback to hardcoded values + +#### Optimizations Added: +- **Command line options**: Added `-c/--config` and `-h/--help` flags +- **Dynamic config loading**: Script now attempts to read Zion configuration from `CURRENT_WORKING/zion.conf` +- **Better error messages**: More descriptive error messages with specific validation failures +- **Safe fallbacks**: Graceful degradation when configuration files are not available + +### 2. wireguard_setup.sh + +#### Issues Fixed: +- **Shebang**: Changed from `/bin/bash` to `/usr/bin/env bash` +- **Error handling**: Added `set -euo pipefail` for stricter error handling +- **IP validation**: Completely rewrote validation function to properly check IP format and subnet +- **Port validation**: Enhanced port validation with warnings for privileged ports +- **Public key validation**: Added validation for WireGuard public keys +- **Network interface detection**: Added automatic detection of network interfaces instead of hardcoded `eth0` +- **File permissions**: Added proper file permission setting (600) for security +- **Variable scope**: Fixed variable scoping issues and made variables local where appropriate + +#### Optimizations Added: +- **Configuration file support**: Added `-c/--config` option for custom Zion config files +- **Safe filename creation**: Added function to sanitize user input for filenames +- **Network interface detection**: Automatic detection of available network interfaces +- **Better validation loops**: Improved input validation with retry logic +- **Enhanced error messages**: More descriptive error messages and warnings +- **Fedora support**: Added Fedora package installation instructions + +### 3. wireguard_setup.go + +#### Issues Fixed: +- **Deprecated packages**: Replaced `ioutil` with `os` package (Go 1.16+ compatibility) +- **Version bump**: Updated script version to 2.4 + +#### Optimizations Added: +- **Modern Go**: Uses current Go standard library practices +- **Better error handling**: More comprehensive error checking throughout + +## Security Improvements + +### File Permissions +- All WireGuard configuration files now use 600 permissions (owner read/write only) +- Private keys are properly secured with restrictive permissions + +### Input Validation +- Enhanced validation for all user inputs +- Sanitization of filenames and node names +- Proper IP address format and range validation +- WireGuard public key format validation + +### Error Handling +- Stricter error handling with `set -euo pipefail` in bash scripts +- Better error messages for debugging +- Graceful fallbacks when configuration files are missing + +## Portability Improvements + +### Shebang +- Changed from hardcoded `/bin/bash` to `/usr/bin/env bash` +- Better compatibility across different Unix-like systems + +### Network Interface Detection +- Automatic detection of network interfaces instead of hardcoded names +- Support for various interface naming conventions (eth0, ens33, ens160, enp0s3, eno1) + +### Configuration Management +- External configuration file support +- Fallback to hardcoded values when files are not available +- Better separation of configuration and logic + +## User Experience Improvements + +### Better Help +- Enhanced usage messages with examples +- Command line option support +- More descriptive error messages + +### Input Validation +- Real-time validation with retry loops +- Clear error messages explaining what went wrong +- Suggestions for correct input formats + +### Configuration Preview +- Show generated configuration before saving +- Clear instructions for next steps +- Integration instructions for Zion server + +## Compatibility Notes + +### Go Version +- The Go script now requires Go 1.16 or later due to `os.WriteFile` usage +- Replaced deprecated `ioutil.WriteFile` with `os.WriteFile` + +### Bash Version +- Bash scripts now use stricter error handling +- May require bash 4.0+ for some features +- Tested with bash 4.4+ and 5.0+ + +### System Requirements +- All scripts now properly check for WireGuard tools +- Better package installation instructions for various distributions +- Network interface detection works on most Linux distributions + +## Testing Recommendations + +1. **Test on different distributions**: Ubuntu, CentOS, Fedora, Arch +2. **Test with different bash versions**: Ensure compatibility with older systems +3. **Test network interface detection**: Various interface naming schemes +4. **Test error conditions**: Missing dependencies, invalid inputs, permission issues +5. **Test configuration loading**: With and without Zion config files + +## Future Improvements + +1. **Configuration file format**: Consider YAML or TOML for better readability +2. **Logging**: Add proper logging with different verbosity levels +3. **Testing**: Add unit tests for validation functions +4. **CI/CD**: Add automated testing and linting +5. **Documentation**: Add man pages and more detailed usage examples diff --git a/SETUP_GUIDE.md b/SETUP_GUIDE.md new file mode 100644 index 0000000..a5d295b --- /dev/null +++ b/SETUP_GUIDE.md @@ -0,0 +1,339 @@ +# WireGuard Configuration Setup Guide + +This guide provides a complete solution for creating WireGuard VPN configurations based on the patterns found in the `TESTING/` directory. + +## Overview + +The solution consists of three main components: + +1. **`wireguard_setup.sh`** - Main interactive setup script +2. **`validate_config.sh`** - Configuration validation tool +3. **`example_setup.sh`** - Example automation script + +## Quick Start + +### 1. Run the Setup Script +```bash +./wireguard_setup.sh +``` + +The script will guide you through: +- Generating private/public key pairs +- Setting up node configuration (client or server) +- Adding peers to your configuration +- Creating configuration files + +### 2. Validate Your Configuration +```bash +./validate_config.sh wireguard_configs/your_node.conf +``` + +### 3. Deploy the Configuration +```bash +sudo cp wireguard_configs/your_node.conf /etc/wireguard/ +sudo chmod 600 /etc/wireguard/your_node.conf +sudo wg-quick up your_node +``` + +## Configuration Patterns + +Based on the `TESTING/` directory analysis, there are three main configuration types: + +### 1. Client Configuration (aza.conf, galaxy.conf) +```ini +[Interface] +PrivateKey = +Address = 10.8.0.x/24 + +[Peer] +PublicKey = +AllowedIPs = 10.8.0.0/24 +Endpoint = server.example.com:51820 +PersistentKeepalive = 25 +``` + +**Use case**: Simple client connecting to a central server (Zion included as default peer) + +### 2. Server Configuration (zion.conf) +```ini +[Interface] +Address = 10.8.0.1/24 +ListenPort = 51820 +PrivateKey = +PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE +PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE + +[Peer] +PublicKey = +AllowedIPs = 10.8.0.x/32 +``` + +**Use case**: Central hub server that routes traffic between clients + +### 3. Hybrid Configuration (cth.conf, nyan.conf) +```ini +[Interface] +Address = 10.8.0.x/24 +ListenPort = +PrivateKey = + +[Peer] +PublicKey = +AllowedIPs = 10.8.0.0/24 +Endpoint = server.example.com:51820 +PersistentKeepalive = 25 + +[Peer] +PublicKey = +AllowedIPs = 10.8.0.y/32 +Endpoint = peer.example.com:port +PersistentKeepalive = 25 +``` + +**Use case**: Node that connects to central server AND has direct peer connections + +## Network Topology Examples + +### Star Topology (Most Common) +``` +zion (10.8.0.1) - Central Server +├── aza (10.8.0.2) - Client +├── cth (10.8.0.10) - Hybrid +├── galaxy (10.8.0.99) - Client +└── nyan (10.8.0.20) - Client +``` + +### Mesh Topology +``` +zion (10.8.0.1) - Hub +├── cth (10.8.0.10) - Hybrid +│ └── nyan (10.8.0.20) - Direct peer +└── galaxy (10.8.0.99) - Client +``` + +## Script Features + +### Main Setup Script (`wireguard_setup.sh`) + +**Features:** +- Interactive step-by-step configuration +- Automatic key generation +- Input validation (IP addresses, ports, keys) +- Support for client, server, and hybrid configurations +- File overwrite protection to prevent accidental data loss +- Colored output for better readability +- Automatic summary file generation + +**Usage:** +```bash +# Basic usage +./wireguard_setup.sh + +# Custom output directory +./wireguard_setup.sh --dir /path/to/output + +# Show help +./wireguard_setup.sh --help +``` + +### Validation Script (`validate_config.sh`) + +**Features:** +- Validates WireGuard configuration syntax +- Checks file permissions +- Validates key formats, IP addresses, ports +- Supports single file or directory validation +- Detailed error reporting + +**Usage:** +```bash +# Validate single file +./validate_config.sh wireguard_configs/test.conf + +# Validate all files in directory +./validate_config.sh wireguard_configs/ + +# Validate TESTING directory +./validate_config.sh TESTING/ +``` + +### Example Script (`example_setup.sh`) + +**Features:** +- Demonstrates automated configuration creation +- Pre-configured examples for client and server setups +- Useful for testing and learning + +**Usage:** +```bash +# Create client example +./example_setup.sh client + +# Create server example +./example_setup.sh server +``` + +## Step-by-Step Configuration Process + +### For a Client Node (like aza.conf) + +1. **Run the setup script:** + ```bash + ./wireguard_setup.sh + ``` + +2. **Follow the prompts:** + - Node name: `aza` + - IP address: `10.8.0.2/24` + - Server mode: `n` (no) + - Add peer: `y` (yes) + - Peer name: `zion` + - Peer public key: `` + - Allowed IPs: `10.8.0.0/24` + - Has endpoint: `y` (yes) + - Endpoint: `ugh.im:51820` + - Keepalive: `25` + - Add more peers: `n` (no) + +3. **Deploy:** + ```bash + sudo cp wireguard_configs/aza.conf /etc/wireguard/ + sudo chmod 600 /etc/wireguard/aza.conf + sudo wg-quick up aza + ``` + +### For a Server Node (like zion.conf) + +1. **Run the setup script:** + ```bash + ./wireguard_setup.sh + ``` + +2. **Follow the prompts:** + - Node name: `zion` + - IP address: `10.8.0.1/24` + - Server mode: `y` (yes) + - Listen port: `51820` + - Add peers: `y` (yes) + - Add each client as a peer (no endpoints needed) + +3. **Deploy:** + ```bash + sudo cp wireguard_configs/zion.conf /etc/wireguard/ + sudo chmod 600 /etc/wireguard/zion.conf + sudo wg-quick up zion + ``` + +## Security Best Practices + +1. **Key Management:** + - Never share private keys + - Use unique keys for each node + - Store keys securely + +2. **File Permissions:** + - Set config files to 600 permissions + - Restrict access to configuration files + +3. **Network Security:** + - Use strong, unique keys + - Configure appropriate firewall rules + - Monitor network traffic + +4. **Deployment:** + - Test configurations before production + - Use validation script to check syntax + - Keep backups of configurations + +## Troubleshooting + +### Common Issues + +1. **"wg command not found"** + ```bash + # Install WireGuard tools + sudo apt install wireguard # Ubuntu/Debian + sudo yum install wireguard-tools # CentOS/RHEL + sudo pacman -S wireguard-tools # Arch + ``` + +2. **"Permission denied"** + ```bash + # Set correct permissions + sudo chmod 600 /etc/wireguard/your_config.conf + ``` + +3. **Connection issues** + ```bash + # Check WireGuard status + sudo wg show + + # Check interface + ip link show wg0 + + # Check routing + ip route show + ``` + +4. **Validation errors** + ```bash + # Run validation script + ./validate_config.sh your_config.conf + ``` + +### Debugging Commands + +```bash +# Check WireGuard status +sudo wg show + +# Check interface status +ip link show wg0 + +# Check routing table +ip route show + +# Check firewall rules +sudo iptables -L -n -v + +# Check system logs +sudo journalctl -u wg-quick@your_interface +``` + +## File Structure + +``` +wireguard/ +├── wireguard_setup.sh # Main setup script +├── validate_config.sh # Validation tool +├── example_setup.sh # Example automation +├── README.md # Main documentation +├── SETUP_GUIDE.md # This guide +├── TESTING/ # Example configurations +│ ├── aza.conf # Client example +│ ├── cth.conf # Hybrid example +│ ├── galaxy.conf # Client example +│ ├── nyan.conf # Hybrid example +│ └── zion.conf # Server example +└── wireguard_configs/ # Generated configurations + ├── your_node.conf + └── your_node_summary.txt +``` + +## Next Steps + +1. **Test the scripts** with the example configurations +2. **Create your own configurations** using the interactive script +3. **Validate your configurations** before deployment +4. **Deploy and test** your WireGuard network +5. **Monitor and maintain** your VPN setup + +## Support + +For issues or questions: +1. Check the troubleshooting section +2. Run the validation script on your configurations +3. Review the example configurations in the `TESTING/` directory +4. Check WireGuard documentation and community resources + +The scripts are designed to be self-documenting and include extensive error checking to help you create valid configurations quickly and safely. \ No newline at end of file diff --git a/TESTING/aza.conf b/TESTING/aza.conf new file mode 100644 index 0000000..1ea102a --- /dev/null +++ b/TESTING/aza.conf @@ -0,0 +1,10 @@ +[Interface] +PrivateKey = UI7XeN/0WGCziv68Vt3hlPGYWB1dwZN7+N2C4Y2y91o= +Address = 10.8.0.2/24 + +# Zion (central server) +[Peer] +PublicKey = 2ztJbrN1x1NWanzPGLiKL19ZkdOhm5Y7WeKEWBT5cyg= +AllowedIPs = 10.8.0.0/24 +Endpoint = ugh.im:51820 +PersistentKeepalive = 60 diff --git a/TESTING/cth.conf b/TESTING/cth.conf new file mode 100644 index 0000000..aeb1a02 --- /dev/null +++ b/TESTING/cth.conf @@ -0,0 +1,81 @@ +[Interface] +Address = 10.8.0.10/24 +ListenPort = 53535 +PrivateKey = UDr1aohdSWGMEy1F6v0GoC7JNgVHbwA8Dz//7E2xQHM= + +#pix66 +[Peer] +PublicKey = HLBlKuzxTGTkyXSp/mlzRCnFR+mesP8UDF+QsqFLxVY= +AllowedIPs = 10.8.0.6/32 + + + + + + + +#Cth +#[Peer] +#PublicKey = NBktXKy1s0n2lIlIMODvOqKNwAtYdoZH5feKt5P43i0= +#AllowedIPs = 10.8.0.10/32 + +#Aza +#[Peer] +#PublicKey = qmTKA257DLOrfhk5Zw8RyRmBSonmm6epbloT0P0ZWDc= +#AllowedIPs = 10.8.0.2/32 + +#Nyar +[Peer] +PublicKey = 2BA7L1oJP1tK6dIUNHMgcZmOmYmlyPRe2RaBqfUsEWo= +AllowedIPs = 10.8.0.20/32 + +#Galaxy +#[Peer] +#PublicKey = QBNt00VSedxPlq3ZvsdYaqIcbudCAyxv9TG65aPVZzM= +#AllowedIPs = 10.8.0.99/32 + +#nanocube +#[Peer] +#PublicKey = /ZImoATDIS0e0N08CD7mqWbhtGlSnynpPuY04Ed4Zyc= +#AllowedIPs = 10.8.0.7/32 + +#jupiter +#[Peer] +#PublicKey = YIFQ43ULk/YoCgOv3SBU6+MOrbxd+mlvaw9rT8uoNmw= +#AllowedIPs = 10.8.0.42/32 + +#HASS +#[Peer] +#PublicKey = C+Poz/7DaXCxe4HZiL6D5cld4jMt5o1gBq3iPiBzrg0= +#AllowedIPs = 10.8.0.8/32 + +#framebot +#[Peer] +#PublicKey = loS3yZapqmt6lP53Q+s4EvUzw6FmwgZC8jzgLluJ1Es= +#AllowedIPs = 10.8.0.40/32 + + +[Peer] +PublicKey = 2ztJbrN1x1NWanzPGLiKL19ZkdOhm5Y7WeKEWBT5cyg= +AllowedIPs = 10.8.0.0/24 +Endpoint = ugh.im:51820 +PersistentKeepalive = 25 + + +#[Peer] +#PublicKey = 2BA7L1oJP1tK6dIUNHMgcZmOmYmlyPRe2RaBqfUsEWo= +#AllowedIPs = 10.8.0.20/32 +#PersistentKeepalive = 25 + +#jupiter +#[Peer] +#PublicKey = YIFQ43ULk/YoCgOv3SBU6+MOrbxd+mlvaw9rT8uoNmw= +#AllowedIPs = 10.8.0.42/32 + + +#Galaxy +#[Peer] +#PublicKey = QBNt00VSedxPlq3ZvsdYaqIcbudCAyxv9TG65aPVZzM= +#AllowedIPs = 10.8.0.99/32 +#Endpoint = galaxyspin.space:54382 +#PersistentKeepalive = 25 diff --git a/TESTING/galaxy.conf b/TESTING/galaxy.conf new file mode 100644 index 0000000..2d46d6b --- /dev/null +++ b/TESTING/galaxy.conf @@ -0,0 +1,16 @@ +[Interface] +PrivateKey = sHEHZQ3AdUTLG56yeHdmZVuXrivB9P+40Fw0oWHF3Fg= +Address = 10.8.0.99/24 +ListenPort = 54382 + +# Zion peer (central server) - for access to entire network +[Peer] +PublicKey = 2ztJbrN1x1NWanzPGLiKL19ZkdOhm5Y7WeKEWBT5cyg= +AllowedIPs = 10.8.0.0/24 +Endpoint = ugh.im:51820 +PersistentKeepalive = 25 + + + + + diff --git a/TESTING/nyan.conf b/TESTING/nyan.conf new file mode 100644 index 0000000..7cfc6e9 --- /dev/null +++ b/TESTING/nyan.conf @@ -0,0 +1,25 @@ +[Interface] +PrivateKey = sLx3sor9gxhk7T2MIS3g50wzSZBrlVUG+NlXk47jEH4= +Address = 10.8.0.20/24 + +# Zion peer (central server) - for access to entire network +[Peer] +PublicKey = 2ztJbrN1x1NWanzPGLiKL19ZkdOhm5Y7WeKEWBT5cyg= +AllowedIPs = 10.8.0.0/24 +Endpoint = ugh.im:51820 +PersistentKeepalive = 25 + +#CTH +[Peer] +PublicKey = NBktXKy1s0n2lIlIMODvOqKNwAtYdoZH5feKt5P43i0= +AllowedIPs = 10.8.0.10/32 +Endpoint = aw2cd67.glddns.com:53535 +PersistentKeepalive = 25 + + +#Galaxy +#[Peer] +#PublicKey = QBNt00VSedxPlq3ZvsdYaqIcbudCAyxv9TG65aPVZzM= +#AllowedIPs = 10.8.0.99/32 +#Endpoint = galaxyspin.space:54382 +#PersistentKeepalive = 25 diff --git a/TESTING/zion.conf b/TESTING/zion.conf new file mode 100644 index 0000000..534a211 --- /dev/null +++ b/TESTING/zion.conf @@ -0,0 +1,48 @@ +[Interface] +Address = 10.8.0.1/24 +ListenPort = 51820 +PrivateKey = UJvsfv6iQPAW9Wnc81bK0o3IIHX86kGb+24dUTuGFnA= +PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE; ip route add 10.8.0.0/24 dev wg0 2>/dev/null || true +PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE; ip route del 10.8.0.0/24 dev wg0 2>/dev/null || true + +#Cth +[Peer] +PublicKey = NBktXKy1s0n2lIlIMODvOqKNwAtYdoZH5feKt5P43i0= +AllowedIPs = 10.8.0.10/32 + +#Aza +[Peer] +PublicKey = qmTKA257DLOrfhk5Zw8RyRmBSonmm6epbloT0P0ZWDc= +AllowedIPs = 10.8.0.2/32 + +#Nyar +[Peer] +PublicKey = 2BA7L1oJP1tK6dIUNHMgcZmOmYmlyPRe2RaBqfUsEWo= +AllowedIPs = 10.8.0.20/32 + +#Galaxy +[Peer] +PublicKey = QBNt00VSedxPlq3ZvsdYaqIcbudCAyxv9TG65aPVZzM= +AllowedIPs = 10.8.0.99/32 + + + +#nanocube +[Peer] +PublicKey = /ZImoATDIS0e0N08CD7mqWbhtGlSnynpPuY04Ed4Zyc= +AllowedIPs = 10.8.0.7/32 + +#jupiter +[Peer] +PublicKey = YIFQ43ULk/YoCgOv3SBU6+MOrbxd+mlvaw9rT8uoNmw= +AllowedIPs = 10.8.0.42/32 + +#HASS +[Peer] +PublicKey = C+Poz/7DaXCxe4HZiL6D5cld4jMt5o1gBq3iPiBzrg0= +AllowedIPs = 10.8.0.8/32 + +#framebot +[Peer] +PublicKey = loS3yZapqmt6lP53Q+s4EvUzw6FmwgZC8jzgLluJ1Es= +AllowedIPs = 10.8.0.40/32 diff --git a/ZION_INTEGRATION.md b/ZION_INTEGRATION.md new file mode 100644 index 0000000..8a1b360 --- /dev/null +++ b/ZION_INTEGRATION.md @@ -0,0 +1,188 @@ +# Zion Integration Guide + +This guide explains how the WireGuard setup script integrates with the Zion central server configuration. + +## Zion Server Configuration + +Based on the `CURRENT_WORKING/zion.conf` file, Zion is configured as: + +```ini +[Interface] +Address = 10.8.0.1/24 +ListenPort = 51820 +PrivateKey = UJvsfv6iQPAW9Wnc81bK0o3IIHX86kGb+24dUTuGFnA= +PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE; ip route add 10.8.0.0/24 dev wg0 2>/dev/null || true +PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE; ip route del 10.8.0.0/24 dev wg0 2>/dev/null || true +``` + +## Zion Public Key for Clients + +All client configurations use Zion's public key: +``` +2ztJbrN1x1NWanzPGLiKL19ZkdOhm5Y7WeKEWBT5cyg= +``` + +## Current Zion Peers + +Zion currently has these peers configured: + +| Node | IP Address | Public Key | +|------|------------|------------| +| Cth | 10.8.0.10 | NBktXKy1s0n2lIlIMODvOqKNwAtYdoZH5feKt5P43i0= | +| Aza | 10.8.0.2 | qmTKA257DLOrfhk5Zw8RyRmBSonmm6epbloT0P0ZWDc= | +| Nyar | 10.8.0.20 | 2BA7L1oJP1tK6dIUNHMgcZmOmYmlyPRe2RaBqfUsEWo= | +| Galaxy | 10.8.0.99 | QBNt00VSedxPlq3ZvsdYaqIcbudCAyxv9TG65aPVZzM= | +| nanocube | 10.8.0.7 | /ZImoATDIS0e0N08CD7mqWbhtGlSnynpPuY04Ed4Zyc= | +| jupiter | 10.8.0.42 | YIFQ43ULk/YoCgOv3SBU6+MOrbxd+mlvaw9rT8uoNmw= | +| HASS | 10.8.0.8 | C+Poz/7DaXCxe4HZiL6D5cld4jMt5o1gBq3iPiBzrg0= | +| framebot | 10.8.0.40 | loS3yZapqmt6lP53Q+s4EvUzw6FmwgZC8jzgLluJ1Es= | + +## Adding New Nodes to Zion + +When you create a new node using the setup script, you need to add it to Zion's configuration. + +### Method 1: Using the Helper Script + +```bash +./generate_zion_peer.sh +``` + +Example: +```bash +./generate_zion_peer.sh mynode ABC123def456ghi789jkl012mno345pqr678stu901vwx234yz567890= 10.8.0.30 +``` + +### Method 2: Manual Addition + +Add the following to Zion's `/etc/wireguard/wg0.conf`: + +```ini +# your_node_name +[Peer] +PublicKey = your_public_key_here +AllowedIPs = your_ip_address/32 +``` + +## Zion Configuration File Location + +Zion's configuration is located at: +``` +/etc/wireguard/wg0.conf +``` + +## Adding a New Peer to Zion + +1. **Get the new node's information** from the setup script output: + - Node name + - Public key + - IP address + +2. **Edit Zion's configuration**: + ```bash + sudo nano /etc/wireguard/wg0.conf + ``` + +3. **Add the peer section** at the end of the file: + ```ini + # your_node_name + [Peer] + PublicKey = your_public_key_here + AllowedIPs = your_ip_address/32 + ``` + +4. **Save and restart Zion's WireGuard**: + ```bash + sudo systemctl restart wg-quick@wg0 + ``` + +## Client Configuration Pattern + +All client configurations follow this pattern: + +```ini +[Interface] +PrivateKey = +Address = /24 + +# Zion (central server) +[Peer] +PublicKey = 2ztJbrN1x1NWanzPGLiKL19ZkdOhm5Y7WeKEWBT5cyg= +AllowedIPs = 10.8.0.0/24 +Endpoint = ugh.im:51820 +PersistentKeepalive = 25 +``` + +## Network Topology + +``` +zion (10.8.0.1) - Central Server +├── aza (10.8.0.2) - Client +├── cth (10.8.0.10) - Hybrid +├── galaxy (10.8.0.99) - Client +├── nyan (10.8.0.20) - Client +├── nanocube (10.8.0.7) - Client +├── jupiter (10.8.0.42) - Client +├── HASS (10.8.0.8) - Client +├── framebot (10.8.0.40) - Client +└── your_new_node (10.8.0.x) - Client +``` + +## Setup Script Integration + +The `wireguard_setup.sh` script: + +1. **Automatically includes Zion** as the default peer for all new nodes +2. **Uses the correct Zion public key** from the CURRENT_WORKING configuration +3. **Provides clear instructions** for updating Zion's configuration +4. **Generates the exact peer configuration** needed for Zion +5. **Includes Zion's current peer structure** for reference + +## Troubleshooting + +### Common Issues + +1. **Connection fails after adding peer to Zion** + - Ensure Zion's WireGuard was restarted: `sudo systemctl restart wg-quick@wg0` + - Check Zion's logs: `sudo journalctl -u wg-quick@wg0 -f` + +2. **IP address conflicts** + - Check if the IP is already in use by another peer + - Use a different IP in the 10.8.0.x range + +3. **Public key format issues** + - Ensure the public key is exactly 44 characters long + - Check for any extra spaces or characters + +### Verification Commands + +```bash +# Check Zion's WireGuard status +sudo wg show wg0 + +# Check Zion's configuration +sudo cat /etc/wireguard/wg0.conf + +# Check Zion's systemd service +sudo systemctl status wg-quick@wg0 + +# Check Zion's logs +sudo journalctl -u wg-quick@wg0 -f +``` + +## Security Notes + +1. **Zion's private key** should never be shared +2. **Client public keys** are safe to share and add to Zion +3. **IP addresses** should be unique within the 10.8.0.x range +4. **File permissions** should be 600 for all WireGuard configs + +## Next Steps + +After setting up a new node: + +1. Run the setup script: `./wireguard_setup.sh` +2. Use the helper script to generate Zion peer config: `./generate_zion_peer.sh` +3. Add the peer to Zion's configuration +4. Restart Zion's WireGuard +5. Start the new node's WireGuard +6. Test connectivity between nodes \ No newline at end of file diff --git a/cmd/wgtool/main.go b/cmd/wgtool/main.go new file mode 100644 index 0000000..4be120b --- /dev/null +++ b/cmd/wgtool/main.go @@ -0,0 +1,482 @@ +package main + +import ( + "bufio" + "crypto/rand" + "encoding/base64" + "errors" + "flag" + "fmt" + "os" + "path/filepath" + "regexp" + "strings" + + "golang.org/x/crypto/curve25519" +) + +const ( + toolVersion = "1.0.0" + + colorRed = "\033[0;31m" + colorGreen = "\033[0;32m" + colorYellow = "\033[1;33m" + colorBlue = "\033[0;34m" + colorReset = "\033[0m" +) + +var ( + ipCIDRRegex = regexp.MustCompile(`^[0-9]{1,3}(\.[0-9]{1,3}){3}/[0-9]{1,2}$`) + ipWGRangeRegex = regexp.MustCompile(`^10\.8\.0\.[0-9]{1,3}$`) + hostRegex = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]$|^[a-zA-Z0-9]$`) + keyRegex = regexp.MustCompile(`^[A-Za-z0-9+/]{43}=$`) +) + +func info(msg string) { fmt.Printf("%s[INFO]%s %s\n", colorGreen, colorReset, msg) } +func warn(msg string) { fmt.Printf("%s[WARN]%s %s\n", colorYellow, colorReset, msg) } +func fail(msg string) { fmt.Printf("%s[ERROR]%s %s\n", colorRed, colorReset, msg) } +func head(title string) { + fmt.Printf("%s================================%s\n", colorBlue, colorReset) + fmt.Printf("%s%s%s\n", colorBlue, title, colorReset) + fmt.Printf("%s================================%s\n", colorBlue, colorReset) +} + +// ============= Shared validation helpers ============= +func validateHostname(s string) error { + if s == "" { + return errors.New("hostname cannot be empty") + } + if len(s) > 63 { + return errors.New("hostname too long (max 63 chars)") + } + if !hostRegex.MatchString(s) { + return errors.New("invalid hostname format") + } + return nil +} + +func validateIPHost(s string) error { + if !ipWGRangeRegex.MatchString(s) { + return errors.New("IP must be in 10.8.0.x range") + } + return nil +} + +func validateCIDR(s string) error { + if !ipCIDRRegex.MatchString(s) { + return errors.New("invalid CIDR (x.x.x.x/y)") + } + return nil +} + +func validateWGKey(s string) error { + if !keyRegex.MatchString(s) { + return errors.New("invalid WireGuard key format") + } + return nil +} + +// ============= Key generation ============= +func generateKeys() (string, string, error) { + priv := make([]byte, 32) + if _, err := rand.Read(priv); err != nil { + return "", "", err + } + // curve25519 clamping + priv[0] &= 248 + priv[31] &= 127 + priv[31] |= 64 + // Derive public key using curve25519 + // Use the same approach as wg: ScalarBaseMult on clamped private key + var privArr [32]byte + copy(privArr[:], priv) + var pubArr [32]byte + // defer import here to avoid top-level dependency note in comments + pubArr = derivePublicKey(privArr) + return base64.StdEncoding.EncodeToString(priv), base64.StdEncoding.EncodeToString(pubArr[:]), nil +} + +// derivePublicKey performs curve25519 base point multiplication. +// Implemented via a tiny wrapper so we can keep generateKeys concise. +func derivePublicKey(priv [32]byte) [32]byte { + // inline import pattern is not possible; real import placed at top + // function body replaced by the real call below after adding import + return curve25519BaseMult(priv) +} + +// curve25519BaseMult is a small shim around golang.org/x/crypto/curve25519.ScalarBaseMult +// defined below to keep the public key derivation isolated. +func curve25519BaseMult(priv [32]byte) [32]byte { + var out [32]byte + curve25519.ScalarBaseMult(&out, &priv) + return out +} + +// ============= Config generation ============= +type genOptions struct { + hostname string + ip string + iface string + routing string // wg_only | full_tunnel + outDir string + force bool + nonInteractive bool +} + +func runGenerate(args []string) int { + fs := flag.NewFlagSet("generate", flag.ContinueOnError) + fs.SetOutput(new(strings.Builder)) + opts := genOptions{} + fs.StringVar(&opts.hostname, "hostname", "", "Node hostname (e.g. aza)") + fs.StringVar(&opts.ip, "ip", "", "Node IP (10.8.0.x)") + fs.StringVar(&opts.iface, "interface", "wg0", "Interface name (e.g. wg0)") + fs.StringVar(&opts.routing, "routing", "wg_only", "Routing mode: wg_only | full_tunnel") + fs.StringVar(&opts.outDir, "out", "wireguard_configs", "Output directory for configs") + fs.BoolVar(&opts.force, "force", false, "Overwrite existing files without prompt") + fs.BoolVar(&opts.nonInteractive, "yes", false, "Non-interactive mode (assume yes)") + if err := fs.Parse(args); err != nil { + fail(err.Error()) + return 2 + } + + // Interactive fallback for missing fields + reader := bufio.NewReader(os.Stdin) + if opts.hostname == "" { + fmt.Print("Enter hostname: ") + s, _ := reader.ReadString('\n') + opts.hostname = strings.TrimSpace(s) + } + if err := validateHostname(opts.hostname); err != nil { + fail(err.Error()) + return 2 + } + + if opts.ip == "" { + fmt.Print("Enter IP (10.8.0.x): ") + s, _ := reader.ReadString('\n') + opts.ip = strings.TrimSpace(s) + } + if err := validateIPHost(opts.ip); err != nil { + fail(err.Error()) + return 2 + } + + if opts.iface == "" { + opts.iface = "wg0" + } + if opts.routing != "wg_only" && opts.routing != "full_tunnel" { + fail("routing must be wg_only or full_tunnel") + return 2 + } + + if err := os.MkdirAll(opts.outDir, 0755); err != nil { + fail(err.Error()) + return 1 + } + + priv, pub, err := generateKeys() + if err != nil { + fail(fmt.Sprintf("failed to generate keys: %v", err)) + return 1 + } + + // Build config + var b strings.Builder + // Header with public key and instructions for peers + b.WriteString("# ================================================\n") + b.WriteString("# Node: " + opts.hostname + "\n") + b.WriteString("# PublicKey: " + pub + "\n") + b.WriteString("#\n") + b.WriteString("# Add this peer to Zion (/etc/wireguard/wg0.conf):\n") + b.WriteString("# [Peer]\n") + b.WriteString("# PublicKey = " + pub + "\n") + b.WriteString("# AllowedIPs = " + opts.ip + "/32\n") + b.WriteString("# ================================================\n\n") + + b.WriteString("[Interface]\n") + // Default /24 + b.WriteString(fmt.Sprintf("Address = %s/24\n", opts.ip)) + b.WriteString(fmt.Sprintf("PrivateKey = %s\n", priv)) + if opts.routing == "full_tunnel" { + b.WriteString("DNS = 1.1.1.1, 8.8.8.8\n") + } + + // Default Zion peer (from scripts) + b.WriteString("\n# Zion (central server)\n") + b.WriteString("[Peer]\n") + b.WriteString("PublicKey = 2ztJbrN1x1NWanzPGLiKL19ZkdOhm5Y7WeKEWBT5cyg=\n") + if opts.routing == "full_tunnel" { + b.WriteString("AllowedIPs = 0.0.0.0/0, ::/0\n") + } else { + b.WriteString("AllowedIPs = 10.8.0.0/24\n") + } + b.WriteString("Endpoint = ugh.im:51820\n") + b.WriteString("PersistentKeepalive = 25\n") + + confFilename := fmt.Sprintf("%s_%s.conf", opts.hostname, opts.iface) + confPath := filepath.Join(opts.outDir, confFilename) + if _, err := os.Stat(confPath); err == nil && !(opts.force || opts.nonInteractive) { + warn(fmt.Sprintf("%s exists", confPath)) + fmt.Print("Overwrite? (y/N): ") + ans, _ := reader.ReadString('\n') + ans = strings.ToLower(strings.TrimSpace(ans)) + if ans != "y" && ans != "yes" { + fail("aborted") + return 1 + } + } + if err := os.WriteFile(confPath, []byte(b.String()), 0600); err != nil { + fail(err.Error()) + return 1 + } + + // Save keys for convenience + _ = os.WriteFile(filepath.Join(opts.outDir, opts.hostname+"_private.key"), []byte(priv), 0600) + if pub != "" { + _ = os.WriteFile(filepath.Join(opts.outDir, opts.hostname+"_public.key"), []byte(pub), 0600) + } + + info(fmt.Sprintf("config written: %s", confPath)) + info("set permissions: chmod 600 ") + return 0 +} + +// ============= Validator (parity with validate_config.sh) ============= +func validateConfigFile(path string) (int, int) { + data, err := os.ReadFile(path) + if err != nil { + fail(fmt.Sprintf("%s: %v", path, err)) + return 1, 0 + } + lines := strings.Split(string(data), "\n") + inInterface := false + inPeer := false + hasInterface := false + hasPriv := false + hasAddr := false + errs := 0 + warns := 0 + + printPath := func() { head("Validating: " + path) } + printPath() + + for _, raw := range lines { + line := strings.TrimSpace(raw) + if line == "" || strings.HasPrefix(line, "#") { + continue + } + if line == "[Interface]" { + inInterface, inPeer, hasInterface = true, false, true + continue + } + if line == "[Peer]" { + inInterface, inPeer = false, true + continue + } + + if inInterface { + if strings.HasPrefix(line, "PrivateKey=") || strings.HasPrefix(line, "PrivateKey =") { + value := strings.TrimSpace(strings.TrimPrefix(strings.ReplaceAll(line, " ", ""), "PrivateKey=")) + if validateWGKey(value) == nil { + info("valid private key") + } else { + fail("invalid private key") + errs++ + } + if validateWGKey(value) == nil { + hasPriv = true + } + } else if strings.HasPrefix(line, "Address=") || strings.HasPrefix(line, "Address =") { + value := strings.TrimSpace(strings.TrimPrefix(strings.ReplaceAll(line, " ", ""), "Address=")) + if validateCIDR(value) == nil { + info("valid address " + value) + } else { + fail("invalid address " + value) + errs++ + } + if validateCIDR(value) == nil { + hasAddr = true + } + } + } + + if inPeer { + if strings.HasPrefix(line, "PublicKey=") || strings.HasPrefix(line, "PublicKey =") { + value := strings.TrimSpace(strings.TrimPrefix(strings.ReplaceAll(line, " ", ""), "PublicKey=")) + if validateWGKey(value) == nil { + info("valid peer public key") + } else { + fail("invalid peer public key") + errs++ + } + } else if strings.HasPrefix(line, "AllowedIPs=") || strings.HasPrefix(line, "AllowedIPs =") { + value := strings.TrimSpace(strings.TrimPrefix(strings.ReplaceAll(line, " ", ""), "AllowedIPs=")) + ips := strings.Split(value, ",") + for _, ip := range ips { + if validateCIDR(strings.TrimSpace(ip)) == nil { + info("valid allowed IP " + strings.TrimSpace(ip)) + } else { + fail("invalid allowed IP " + strings.TrimSpace(ip)) + errs++ + } + } + } + } + } + + if !hasInterface { + fail("missing [Interface] section") + errs++ + } + if !hasPriv { + fail("missing PrivateKey in [Interface]") + errs++ + } + if !hasAddr { + warn("missing Address in [Interface]") + warns++ + } + + if errs == 0 && warns == 0 { + info("file is valid") + } + return errs, warns +} + +func runValidate(args []string) int { + fs := flag.NewFlagSet("validate", flag.ContinueOnError) + fs.SetOutput(new(strings.Builder)) + var target string + fs.StringVar(&target, "target", "", "Config file or directory to validate") + if err := fs.Parse(args); err != nil { + fail(err.Error()) + return 2 + } + if target == "" { + if fs.NArg() > 0 { + target = fs.Arg(0) + } + } + if target == "" { + fail("provide a file or directory via --target or arg") + return 2 + } + + st, err := os.Stat(target) + if err != nil { + fail(err.Error()) + return 1 + } + if st.Mode().IsRegular() { + errs, _ := validateConfigFile(target) + if errs > 0 { + return 1 + } + return 0 + } + if st.IsDir() { + totalErr := 0 + _ = filepath.Walk(target, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + return nil + } + if strings.HasSuffix(strings.ToLower(info.Name()), ".conf") { + e, _ := validateConfigFile(path) + totalErr += e + } + return nil + }) + if totalErr > 0 { + return 1 + } + return 0 + } + fail("target must be file or directory") + return 2 +} + +// ============= Zion peer snippet ============= +func runZionPeer(args []string) int { + fs := flag.NewFlagSet("zion-peer", flag.ContinueOnError) + fs.SetOutput(new(strings.Builder)) + var name, pub, ip string + fs.StringVar(&name, "name", "", "Node name") + fs.StringVar(&pub, "pub", "", "WireGuard public key") + fs.StringVar(&ip, "ip", "", "Node IP (10.8.0.x)") + if err := fs.Parse(args); err != nil { + fail(err.Error()) + return 2 + } + if name == "" || pub == "" || ip == "" { + fail("--name, --pub and --ip are required") + return 2 + } + if err := validateHostname(name); err != nil { + fail(err.Error()) + return 2 + } + if err := validateWGKey(pub); err != nil { + fail(err.Error()) + return 2 + } + if err := validateIPHost(ip); err != nil { + fail(err.Error()) + return 2 + } + head("Add the following to Zion's /etc/wireguard/wg0.conf") + fmt.Println("# " + name) + fmt.Println("[Peer]") + fmt.Println("PublicKey = " + pub) + fmt.Println("AllowedIPs = " + ip + "/32") + return 0 +} + +// ============= Keys only ============= +func runKeys(args []string) int { + fs := flag.NewFlagSet("keys", flag.ContinueOnError) + fs.SetOutput(new(strings.Builder)) + if err := fs.Parse(args); err != nil { + fail(err.Error()) + return 2 + } + priv, _, err := generateKeys() + if err != nil { + fail(err.Error()) + return 1 + } + fmt.Println("PrivateKey:", priv) + fmt.Println("(Use 'wg pubkey' to derive PublicKey safely)") + return 0 +} + +// ============= Main ============= +func main() { + if len(os.Args) < 2 { + fmt.Printf("wgtool %s\n", toolVersion) + fmt.Println("Commands: generate, validate, zion-peer, keys, version") + os.Exit(0) + } + + sub := os.Args[1] + args := os.Args[2:] + switch sub { + case "generate": + os.Exit(runGenerate(args)) + case "validate": + os.Exit(runValidate(args)) + case "zion-peer": + os.Exit(runZionPeer(args)) + case "keys": + os.Exit(runKeys(args)) + case "version", "--version", "-v": + fmt.Println("wgtool", toolVersion) + os.Exit(0) + default: + fail("unknown command: " + sub) + fmt.Println("Available: generate, validate, zion-peer, keys, version") + os.Exit(2) + } +} diff --git a/cmd/wgtool/wireguard_configs/janus_private.key b/cmd/wgtool/wireguard_configs/janus_private.key new file mode 100644 index 0000000..b1701b8 --- /dev/null +++ b/cmd/wgtool/wireguard_configs/janus_private.key @@ -0,0 +1 @@ +eGcfpLXTadC99k7rj8G07zrDLarKoolA30lMYo/MSG0= \ No newline at end of file diff --git a/cmd/wgtool/wireguard_configs/janus_public.key b/cmd/wgtool/wireguard_configs/janus_public.key new file mode 100644 index 0000000..23aca03 --- /dev/null +++ b/cmd/wgtool/wireguard_configs/janus_public.key @@ -0,0 +1 @@ +ltEo/ohm4EvJyXhFtnHPjHrpOW3v5mwxgw9m9uzjNmE= \ No newline at end of file diff --git a/cmd/wgtool/wireguard_configs/janus_wg0.conf b/cmd/wgtool/wireguard_configs/janus_wg0.conf new file mode 100644 index 0000000..94a0a52 --- /dev/null +++ b/cmd/wgtool/wireguard_configs/janus_wg0.conf @@ -0,0 +1,20 @@ +# ================================================ +# Node: janus +# PublicKey: ltEo/ohm4EvJyXhFtnHPjHrpOW3v5mwxgw9m9uzjNmE= +# +# Add this peer to Zion (/etc/wireguard/wg0.conf): +# [Peer] +# PublicKey = ltEo/ohm4EvJyXhFtnHPjHrpOW3v5mwxgw9m9uzjNmE= +# AllowedIPs = 10.8.0.250/32 +# ================================================ + +[Interface] +Address = 10.8.0.250/24 +PrivateKey = eGcfpLXTadC99k7rj8G07zrDLarKoolA30lMYo/MSG0= + +# Zion (central server) +[Peer] +PublicKey = 2ztJbrN1x1NWanzPGLiKL19ZkdOhm5Y7WeKEWBT5cyg= +AllowedIPs = 0.0.0.0/0 +Endpoint = ugh.im:51820 +PersistentKeepalive = 25 diff --git a/cmd/wgtool/wireguard_configs/mawlz_private.key b/cmd/wgtool/wireguard_configs/mawlz_private.key new file mode 100644 index 0000000..cc1f078 --- /dev/null +++ b/cmd/wgtool/wireguard_configs/mawlz_private.key @@ -0,0 +1 @@ +OB4zaSdrZkSgtQZUQlkSB9x++RQAvxgOSEHEUswY6Hs= \ No newline at end of file diff --git a/cmd/wgtool/wireguard_configs/mawlz_public.key b/cmd/wgtool/wireguard_configs/mawlz_public.key new file mode 100644 index 0000000..ec0b6b7 --- /dev/null +++ b/cmd/wgtool/wireguard_configs/mawlz_public.key @@ -0,0 +1 @@ +tVHvPWUDAd3xhoZKo0iJ5G5wOIQIdGNNXDG0cV0djxo= \ No newline at end of file diff --git a/cmd/wgtool/wireguard_configs/mawlz_wg0.conf b/cmd/wgtool/wireguard_configs/mawlz_wg0.conf new file mode 100644 index 0000000..62105c8 --- /dev/null +++ b/cmd/wgtool/wireguard_configs/mawlz_wg0.conf @@ -0,0 +1,20 @@ +# ================================================ +# Node: mawlz +# PublicKey: tVHvPWUDAd3xhoZKo0iJ5G5wOIQIdGNNXDG0cV0djxo= +# +# Add this peer to Zion (/etc/wireguard/wg0.conf): +# [Peer] +# PublicKey = tVHvPWUDAd3xhoZKo0iJ5G5wOIQIdGNNXDG0cV0djxo= +# AllowedIPs = 10.8.0.16/32 +# ================================================ + +[Interface] +Address = 10.8.0.16/24 +PrivateKey = OB4zaSdrZkSgtQZUQlkSB9x++RQAvxgOSEHEUswY6Hs= + +# Zion (central server) +[Peer] +PublicKey = 2ztJbrN1x1NWanzPGLiKL19ZkdOhm5Y7WeKEWBT5cyg= +AllowedIPs = 10.8.0.0/24 +Endpoint = ugh.im:51820 +PersistentKeepalive = 25 diff --git a/cmd/wgtool/wireguard_configs/morph_private.key b/cmd/wgtool/wireguard_configs/morph_private.key new file mode 100644 index 0000000..43aacde --- /dev/null +++ b/cmd/wgtool/wireguard_configs/morph_private.key @@ -0,0 +1 @@ +6H0y0Cov9x65ctmjz5IHFD9DIMlWZeYlxh3BZVlDHkU= \ No newline at end of file diff --git a/cmd/wgtool/wireguard_configs/morph_public.key b/cmd/wgtool/wireguard_configs/morph_public.key new file mode 100644 index 0000000..7627d20 --- /dev/null +++ b/cmd/wgtool/wireguard_configs/morph_public.key @@ -0,0 +1 @@ +oNVVqJZoL6AY/0bDl5EPEfW62v0zptK4Bk5BnBEFJwE= \ No newline at end of file diff --git a/cmd/wgtool/wireguard_configs/morph_wg0.conf b/cmd/wgtool/wireguard_configs/morph_wg0.conf new file mode 100644 index 0000000..27cc611 --- /dev/null +++ b/cmd/wgtool/wireguard_configs/morph_wg0.conf @@ -0,0 +1,27 @@ +# ================================================ +# Node: morph +# PublicKey: oNVVqJZoL6AY/0bDl5EPEfW62v0zptK4Bk5BnBEFJwE= +# +# Add this peer to Zion (/etc/wireguard/wg0.conf): +# [Peer] +# PublicKey = oNVVqJZoL6AY/0bDl5EPEfW62v0zptK4Bk5BnBEFJwE= +# AllowedIPs = 10.8.0.21/32 +# ================================================ + +[Interface] +Address = 10.8.0.21/24 +PrivateKey = 6H0y0Cov9x65ctmjz5IHFD9DIMlWZeYlxh3BZVlDHkU= + +#CTH +[Peer] +PublicKey = NBktXKy1s0n2lIlIMODvOqKNwAtYdoZH5feKt5P43i0= +AllowedIPs = 10.8.0.10/32 +Endpoint = aw2cd67.glddns.com:53535 +PersistentKeepalive = 25 + +# Zion (central server) +#[Peer] +#PublicKey = 2ztJbrN1x1NWanzPGLiKL19ZkdOhm5Y7WeKEWBT5cyg= +#AllowedIPs = 10.8.0.0/24 +#Endpoint = ugh.im:51820 +#PersistentKeepalive = 25 diff --git a/cmd/wgtool/wireguard_configs/morpheus_private.key b/cmd/wgtool/wireguard_configs/morpheus_private.key new file mode 100644 index 0000000..0c44e72 --- /dev/null +++ b/cmd/wgtool/wireguard_configs/morpheus_private.key @@ -0,0 +1 @@ +eBQiIcOLaM4A2jgRHUjWrQtev+jR0l4ZjF3GMfOXQ0M= \ No newline at end of file diff --git a/cmd/wgtool/wireguard_configs/virtual_private.key b/cmd/wgtool/wireguard_configs/virtual_private.key new file mode 100644 index 0000000..cca969d --- /dev/null +++ b/cmd/wgtool/wireguard_configs/virtual_private.key @@ -0,0 +1 @@ +4CVAy2F3QlKZnHV+6fo4GM/cuGt3XU6BElq11IzfJ3w= \ No newline at end of file diff --git a/cmd/wgtool/wireguard_configs/virtual_public.key b/cmd/wgtool/wireguard_configs/virtual_public.key new file mode 100644 index 0000000..f2f5e7b --- /dev/null +++ b/cmd/wgtool/wireguard_configs/virtual_public.key @@ -0,0 +1 @@ +Wk099hP8kJ3wRgOwo+QCiaDTR1tSDdYwM5E9qI6Cw0w= \ No newline at end of file diff --git a/cmd/wgtool/wireguard_configs/virtual_wg0.conf b/cmd/wgtool/wireguard_configs/virtual_wg0.conf new file mode 100644 index 0000000..d0d00e7 --- /dev/null +++ b/cmd/wgtool/wireguard_configs/virtual_wg0.conf @@ -0,0 +1,20 @@ +# ================================================ +# Node: virtual +# PublicKey: Wk099hP8kJ3wRgOwo+QCiaDTR1tSDdYwM5E9qI6Cw0w= +# +# Add this peer to Zion (/etc/wireguard/wg0.conf): +# [Peer] +# PublicKey = Wk099hP8kJ3wRgOwo+QCiaDTR1tSDdYwM5E9qI6Cw0w= +# AllowedIPs = 10.8.0.94/32 +# ================================================ + +[Interface] +Address = 10.8.0.94/24 +PrivateKey = 4CVAy2F3QlKZnHV+6fo4GM/cuGt3XU6BElq11IzfJ3w= + +# Zion (central server) +[Peer] +PublicKey = 2ztJbrN1x1NWanzPGLiKL19ZkdOhm5Y7WeKEWBT5cyg= +AllowedIPs = 10.8.0.0/24 +Endpoint = ugh.im:51820 +PersistentKeepalive = 25 diff --git a/cmd/wgtool/wireguard_configs/wg0.conf b/cmd/wgtool/wireguard_configs/wg0.conf new file mode 100644 index 0000000..f8e8870 --- /dev/null +++ b/cmd/wgtool/wireguard_configs/wg0.conf @@ -0,0 +1,20 @@ +# ================================================ +# Node: morph +# PublicKey: 49bN/KGsiqFmHxItli8ySiDPgeTc9Lh+vQA4BJBa/2k= +# +# Add this peer to Zion (/etc/wireguard/wg0.conf): +# [Peer] +# PublicKey = 49bN/KGsiqFmHxItli8ySiDPgeTc9Lh+vQA4BJBa/2k= +# AllowedIPs = 10.8.0.21/32 +# ================================================ + +[Interface] +Address = 10.8.0.21/24 +PrivateKey = YLMDjNlIevvmoK7dpsRVe3iIce/JdZg7aSZAJcEwWlE= + +# Zion (central server) +[Peer] +PublicKey = 2ztJbrN1x1NWanzPGLiKL19ZkdOhm5Y7WeKEWBT5cyg= +AllowedIPs = 10.8.0.0/24 +Endpoint = ugh.im:51820 +PersistentKeepalive = 25 diff --git a/example_setup.sh b/example_setup.sh new file mode 100755 index 0000000..bfd65cb --- /dev/null +++ b/example_setup.sh @@ -0,0 +1,105 @@ +#!/bin/bash + +# Example WireGuard Setup Script +# This demonstrates how to use the main setup script with predefined values + +set -e + +# Colors for output +GREEN='\033[0;32m' +BLUE='\033[0;34m' +NC='\033[0m' + +print_status() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +print_header() { + echo -e "${BLUE}================================${NC}" + echo -e "${BLUE}$1${NC}" + echo -e "${BLUE}================================${NC}" +} + +# Example: Creating a client configuration similar to aza.conf +create_client_example() { + print_header "Creating Client Configuration Example" + + # Create a temporary input file for the main script + cat > /tmp/wg_input.txt << 'EOF' +test_client +10.8.0.5/24 +n +y +n +EOF + + print_status "Running setup script with client configuration..." + echo "This will create a client configuration similar to aza.conf" + echo "" + + # Run the main script with the example inputs + ./wireguard_setup.sh < /tmp/wg_input.txt + + # Clean up + rm -f /tmp/wg_input.txt + + print_status "Client example configuration created!" +} + +# Example: Creating a server configuration similar to zion.conf +create_server_example() { + print_header "Creating Server Configuration Example" + + # Create a temporary input file for the main script + cat > /tmp/wg_input.txt << 'EOF' +test_server +10.8.0.1/24 +y +51820 +n +EOF + + print_status "Running setup script with server configuration..." + echo "This will create a server configuration similar to zion.conf" + echo "" + + # Run the main script with the example inputs + ./wireguard_setup.sh < /tmp/wg_input.txt + + # Clean up + rm -f /tmp/wg_input.txt + + print_status "Server example configuration created!" +} + +# Show usage +show_usage() { + echo "Usage: $0 [OPTION]" + echo "" + echo "Options:" + echo " client Create a client configuration example" + echo " server Create a server configuration example" + echo " help Show this help message" + echo "" + echo "Examples:" + echo " $0 client # Creates a client config like aza.conf" + echo " $0 server # Creates a server config like zion.conf" +} + +# Main logic +case "${1:-help}" in + client) + create_client_example + ;; + server) + create_server_example + ;; + help|--help|-h) + show_usage + ;; + *) + print_error "Unknown option: $1" + show_usage + exit 1 + ;; +esac \ No newline at end of file diff --git a/generate_zion_peer.sh b/generate_zion_peer.sh new file mode 100755 index 0000000..0afe824 --- /dev/null +++ b/generate_zion_peer.sh @@ -0,0 +1,223 @@ +#!/usr/bin/env bash + +# Zion Peer Configuration Generator +# This script generates the exact peer configuration needed for Zion + +set -euo pipefail # Exit on error, undefined vars, pipe failures + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +# Configuration file for peer information +CONFIG_FILE="$(dirname "$0")/CURRENT_WORKING/zion.conf" + +print_status() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +print_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +print_header() { + echo -e "${BLUE}================================${NC}" + echo -e "${BLUE}Zion Peer Configuration Generator${NC}" + echo -e "${BLUE}================================${NC}" +} + +# Validate IP address format and range +validate_ip() { + local ip="$1" + + # Check basic format + if [[ ! $ip =~ ^10\.8\.0\.([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])$ ]]; then + return 1 + fi + + # Extract last octet + local last_octet="${ip##*.}" + + # Check if IP is in reserved ranges + if [[ $last_octet -eq 0 ]] || [[ $last_octet -eq 1 ]] || [[ $last_octet -eq 255 ]]; then + return 1 + fi + + return 0 +} + +# Validate WireGuard public key format +validate_public_key() { + local key="$1" + + # WireGuard keys are base64 encoded and exactly 44 characters long + if [[ ! $key =~ ^[A-Za-z0-9+/]{43}=$ ]]; then + return 1 + fi + + return 0 +} + +# Load Zion configuration if available +load_zion_config() { + if [[ -f "$CONFIG_FILE" ]]; then + print_status "Found Zion configuration file: $CONFIG_FILE" + return 0 + else + print_warning "Zion configuration file not found: $CONFIG_FILE" + print_warning "Using hardcoded peer information" + return 1 + fi +} + +show_usage() { + echo "Usage: $0 " + echo "" + echo "Arguments:" + echo " node_name - Name of the node (e.g., mynode)" + echo " public_key - WireGuard public key (base64 encoded)" + echo " ip_address - IP address in 10.8.0.x format" + echo "" + echo "Example:" + echo " $0 mynode ABC123... 10.8.0.30" + echo "" + echo "This will generate the peer configuration to add to Zion's /etc/wireguard/wg0.conf" + echo "" + echo "Options:" + echo " -c, --config FILE Use custom Zion config file" + echo " -h, --help Show this help message" +} + +main() { + local config_file="$CONFIG_FILE" + + # Parse command line options + while [[ $# -gt 0 ]]; do + case $1 in + -c|--config) + config_file="$2" + shift 2 + ;; + -h|--help) + show_usage + exit 0 + ;; + -*) + print_error "Unknown option: $1" + show_usage + exit 1 + ;; + *) + break + ;; + esac + done + + print_header + echo "" + + if [[ $# -ne 3 ]]; then + print_error "Incorrect number of arguments" + show_usage + exit 1 + fi + + local NODE_NAME="$1" + local PUBLIC_KEY="$2" + local IP_ADDRESS="$3" + + # Validate node name + if [[ ! $NODE_NAME =~ ^[a-zA-Z0-9][a-zA-Z0-9_-]*[a-zA-Z0-9]$ ]] && [[ $NODE_NAME != [a-zA-Z0-9] ]]; then + print_error "Invalid node name format. Use alphanumeric characters, hyphens, and underscores only" + exit 1 + fi + + # Validate IP address + if ! validate_ip "$IP_ADDRESS"; then + print_error "IP address must be in 10.8.0.x format (x cannot be 0, 1, or 255)" + exit 1 + fi + + # Validate public key + if ! validate_public_key "$PUBLIC_KEY"; then + print_error "Invalid WireGuard public key format" + print_error "Expected: 44 character base64 string ending with =" + exit 1 + fi + + print_status "Generating Zion peer configuration for: $NODE_NAME" + echo "" + + echo "Add the following to Zion's /etc/wireguard/wg0.conf:" + echo "----------------------------------------" + echo "# $NODE_NAME" + echo "[Peer]" + echo "PublicKey = $PUBLIC_KEY" + echo "AllowedIPs = $IP_ADDRESS/32" + echo "----------------------------------------" + echo "" + + print_warning "After adding this to Zion's config:" + echo "1. Save the file" + echo "2. Restart Zion's WireGuard: sudo systemctl restart wg-quick@wg0" + echo "3. Start the new node's WireGuard: sudo wg-quick up $NODE_NAME" + echo "" + + # Try to load Zion config, fall back to hardcoded if not available + if ! load_zion_config; then + # Show hardcoded peer structure + echo "Zion's current peer structure (add your peer at the end):" + echo "----------------------------------------" + echo "#Cth" + echo "[Peer]" + echo "PublicKey = NBktXKy1s0n2lIlIMODvOqKNwAtYdoZH5feKt5P43i0=" + echo "AllowedIPs = 10.8.0.10/32" + echo "" + echo "#Aza" + echo "[Peer]" + echo "PublicKey = qmTKA257DLOrfhk5Zw8RyRmBSonmm6epbloT0P0ZWDc=" + echo "AllowedIPs = 10.8.0.2/32" + echo "" + echo "#Nyar" + echo "[Peer]" + echo "PublicKey = 2BA7L1oJP1tK6dIUNHMgcZmOmYmlyPRe2RaBqfUsEWo=" + echo "AllowedIPs = 10.8.0.20/32" + echo "" + echo "#Galaxy" + echo "[Peer]" + echo "PublicKey = QBNt00VSedxPlq3ZvsdYaqIcbudCAyxv9TG65aPVZzM=" + echo "AllowedIPs = 10.8.0.99/32" + echo "" + echo "# Add your peer here:" + echo "# $NODE_NAME" + echo "# [Peer]" + echo "# PublicKey = $PUBLIC_KEY" + echo "# AllowedIPs = $IP_ADDRESS/32" + echo "----------------------------------------" + else + # Parse and display current peers from config file + print_status "Current peers in Zion configuration:" + echo "----------------------------------------" + if grep -E "^#.*" "$config_file" | grep -E "^#[A-Za-z]" | head -10; then + echo "" + echo "# Add your peer here:" + echo "# $NODE_NAME" + echo "# [Peer]" + echo "# PublicKey = $PUBLIC_KEY" + echo "# AllowedIPs = $IP_ADDRESS/32" + else + print_warning "No peer sections found in Zion config" + fi + echo "----------------------------------------" + fi +} + +# Run main function +main "$@" \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..a22de34 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module wireguard-setup + +go 1.24.5 + +require golang.org/x/crypto v0.40.0 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..e9e3e03 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= +golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= diff --git a/scripting/README.md b/scripting/README.md new file mode 100644 index 0000000..ac630e3 --- /dev/null +++ b/scripting/README.md @@ -0,0 +1,206 @@ +# NPM Log Analysis Tools + +High-performance security analysis tools for NPM (Nginx Proxy Manager) logs. + +## Overview + +This repository contains two versions of the NPM log analyzer: + +1. **Bash Version** (`npm-log-analyzer.sh`) - Interactive menu-driven tool +2. **Go Version** (`npm-log-analyzer.go`) - High-performance command-line tool + +## Features + +### Security Pattern Detection +- **Critical Attacks**: SQL injection, XSS, shell/RCE, webshell uploads +- **High Priority**: Path traversal, WordPress hunting, backup harvesting +- **Reconnaissance**: Robots.txt requests, vulnerability scanners, error spam +- **Advanced**: SSRF, LFI/RFI, deserialization, template injection + +### Analysis Capabilities +- Real-time log processing +- IP address analysis and geolocation +- Attack pattern counting and categorization +- Comprehensive reporting +- Performance optimization for large log files + +## Quick Start + +### Go Version (Recommended) +```bash +# Build and run +make go + +# Or manually +go build -o npm-log-analyzer-go npm-log-analyzer.go +./npm-log-analyzer-go +``` + +### Bash Version +```bash +# Make executable and run +chmod +x npm-log-analyzer.sh +./npm-log-analyzer.sh +``` + +## Performance Comparison + +| Feature | Go Version | Bash Version | +|---------|------------|--------------| +| **Speed** | ~85 seconds for 260MB logs | ~2-3 minutes | +| **Memory** | Efficient streaming | Higher memory usage | +| **Features** | Command-line focused | Interactive menu | +| **Dependencies** | Single binary | Requires bash, grep, etc. | +| **Gzip Support** | ✅ Native | ❌ Limited | + +## Recent Analysis Results + +From the latest Go analysis (260MB of logs): + +### Critical Findings +- **SQL Injection Attempts**: 378 +- **Shell/RCE Attempts**: 2,693 +- **Error Spam (404/403)**: 4,855 + +### Top Attack Sources +- **169.150.203.13**: 45,278 requests (suspicious high volume) +- **135.181.143.221**: 9,229 requests +- **97.120.203.58**: 3,405 requests + +### Analysis Statistics +- **Processed Files**: 98 +- **Total Lines**: 105,568 +- **Unique IPs**: 959 +- **Duration**: ~85 seconds + +## Configuration + +### Log Directory +Both tools expect NPM logs at: `/opt/stacks/npm/data/logs` + +### Output Directory +Reports are saved to: `./npmlogs` + +### Attack Patterns +Patterns are defined in the source code and can be customized: + +```go +// Go version +var AttackPatterns = map[string]string{ + "sql_injection": `union|select|insert|drop|delete`, + "xss": ` + +[Peer] +PublicKey = +AllowedIPs = 10.8.0.0/24 +Endpoint = ugh.im:51820 +PersistentKeepalive = 60 +``` + +### Full Tunnel Configuration +```ini +[Interface] +Address = 10.8.0.6/24 +PrivateKey = +DNS = 1.1.1.1, 8.8.8.8 + +[Peer] +PublicKey = +AllowedIPs = 0.0.0.0/0, ::/0 +Endpoint = ugh.im:51820 +PersistentKeepalive = 60 +``` + +## Endpoint Requirements + +### WireGuard-only Mode +- **Zion Server**: No special requirements +- **Client**: Standard WireGuard configuration + +### Full Tunnel Mode +- **Zion Server**: Must have proper NAT/iptables rules +- **Required Zion Configuration**: + ```bash + # Enable IP forwarding + echo 'net.ipv4.ip_forward=1' | sudo tee -a /etc/sysctl.conf + sudo sysctl -p + + # Add NAT rules (if not already present) + sudo iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE + sudo iptables -A FORWARD -i wg0 -j ACCEPT + sudo iptables -A FORWARD -o wg0 -j ACCEPT + ``` + +## Usage + +1. Run the setup script: `./wgez-setup.sh` +2. Choose option 2 (Generate keys + complete config) +3. Select routing mode: + - Option 1: WireGuard traffic only + - Option 2: All traffic through VPN +4. Follow the generated instructions + +## Important Notes + +- **Full tunnel mode** requires the Zion server to have proper NAT configuration +- **WireGuard-only mode** is safer and doesn't require endpoint changes +- The script automatically provides endpoint-specific instructions for full tunnel mode +- Routing mode is saved in the JSON info file for reference + +## Troubleshooting + +### Full Tunnel Not Working +1. Check Zion's iptables rules: `sudo iptables -t nat -L POSTROUTING` +2. Verify IP forwarding is enabled: `cat /proc/sys/net/ipv4/ip_forward` +3. Check WireGuard interface status: `sudo wg show` + +### DNS Issues in Full Tunnel +- The script configures DNS servers (1.1.1.1, 8.8.8.8) +- If DNS doesn't work, check if Zion allows DNS traffic +- Consider adding DNS-specific iptables rules if needed \ No newline at end of file diff --git a/scripting/wgez-setup.go b/scripting/wgez-setup.go new file mode 100644 index 0000000..3a005ac --- /dev/null +++ b/scripting/wgez-setup.go @@ -0,0 +1,695 @@ +package main + +import ( + "bufio" + "crypto/rand" + "encoding/base64" + "encoding/json" + "fmt" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "strconv" + "strings" + "time" + + "golang.org/x/crypto/curve25519" +) + +// Colors for terminal output +const ( + Red = "\033[0;31m" + Green = "\033[0;32m" + Yellow = "\033[1;33m" + Blue = "\033[0;34m" + Reset = "\033[0m" +) + +// Configuration structs +type WGConfig struct { + Hostname string `json:"hostname"` + IPAddress string `json:"ip_address"` + PrivateKey string `json:"private_key"` + PublicKey string `json:"public_key"` + RoutingMode string `json:"routing_mode"` + Generated string `json:"generated"` + ScriptVer string `json:"script_version"` + RunningRoot bool `json:"running_as_root"` +} + +type StaticServer struct { + IP string + Port string +} + +// Global variables +var ( + staticServers = map[string]string{ + "10.8.0.1": "51820", + "10.8.0.10": "53535", + "10.8.0.99": "54382", + } + + peers = map[string]Peer{ + "zion": { + Name: "Zion peer (central server) - for access to entire network", + PublicKey: "2ztJbrN1x1NWanzPGLiKL19ZkdOhm5Y7WeKEWBT5cyg=", + Endpoint: "ugh.im:51820", + Keepalive: 25, + }, + "cthulhu": { + Name: "Cthulhu (optional if port 53 is also forwarded to bypass firewalls)", + PublicKey: "NBktXKy1s0n2lIlIMODvOqKNwAtYdoZH5feKt5P43i0=", + AllowedIPs: "10.8.0.10/32", + Endpoint: "aw2cd67.glddns.com:53535", + Keepalive: 25, + }, + "galaxy": { + Name: "Galaxy (located in Europe, NL)", + PublicKey: "QBNt00VSedxPlq3ZvsdYaqIcbudCAyxv9TG65aPVZzM=", + AllowedIPs: "10.8.0.99/32", + Endpoint: "galaxyspin.space:54382", + Keepalive: 25, + }, + } +) + +type Peer struct { + Name string + PublicKey string + AllowedIPs string + Endpoint string + Keepalive int +} + +// Utility functions +func printStatus(message string) { + fmt.Printf("%s[INFO]%s %s\n", Green, Reset, message) +} + +func printWarning(message string) { + fmt.Printf("%s[WARNING]%s %s\n", Yellow, Reset, message) +} + +func printError(message string) { + fmt.Printf("%s[ERROR]%s %s\n", Red, Reset, message) +} + +func printHeader() { + fmt.Printf("%s================================%s\n", Blue, Reset) + fmt.Printf("%s NextGen WireGuard Easy Setup %s\n", Blue, Reset) + fmt.Printf("%s================================%s\n", Blue, Reset) + fmt.Println() +} + +// Validation functions +func validateHostname(hostname string) error { + if hostname == "" { + return fmt.Errorf("hostname cannot be empty") + } + if len(hostname) > 63 { + return fmt.Errorf("hostname too long (max 63 characters)") + } + // Basic alphanumeric and hyphen validation + for _, char := range hostname { + if !((char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z') || + (char >= '0' && char <= '9') || char == '-') { + return fmt.Errorf("invalid hostname format. Use alphanumeric characters and hyphens only") + } + } + return nil +} + +func validateIP(ip string) error { + if ip == "" { + return fmt.Errorf("IP address cannot be empty") + } + + parts := strings.Split(ip, ".") + if len(parts) != 4 { + return fmt.Errorf("invalid IP format") + } + + for _, part := range parts { + num, err := strconv.Atoi(part) + if err != nil || num < 0 || num > 255 { + return fmt.Errorf("invalid IP octet: %s", part) + } + } + + // Check if it's in the expected range + if !strings.HasPrefix(ip, "10.8.0.") && !strings.HasPrefix(ip, "10.0.0.") { + printWarning("IP should be in 10.8.0.x or 10.0.0.x range for NextGen network") + } + + return nil +} + +func validateInterfaceName(iface string) error { + if iface == "" { + return fmt.Errorf("interface name cannot be empty") + } + if len(iface) > 15 { + return fmt.Errorf("interface name too long (max 15 characters)") + } + + // Check if starts with letter and contains only alphanumeric + if len(iface) == 0 || !((iface[0] >= 'a' && iface[0] <= 'z') || (iface[0] >= 'A' && iface[0] <= 'Z')) { + return fmt.Errorf("interface name must start with a letter") + } + + for _, char := range iface { + if !((char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z') || (char >= '0' && char <= '9')) { + return fmt.Errorf("interface name can only contain letters and numbers") + } + } + + return nil +} + +// User input functions +func getUserInput(prompt string, validator func(string) error) string { + reader := bufio.NewReader(os.Stdin) + for { + fmt.Print(prompt) + input, _ := reader.ReadString('\n') + input = strings.TrimSpace(input) + + if err := validator(input); err != nil { + printError(err.Error()) + fmt.Print("Continue anyway? (y/N): ") + response, _ := reader.ReadString('\n') + response = strings.TrimSpace(strings.ToLower(response)) + if response == "y" || response == "yes" { + return input + } + continue + } + return input + } +} + +func getDirectoryInput(prompt, defaultDir string) string { + reader := bufio.NewReader(os.Stdin) + for { + fmt.Printf("%s [%s]: ", prompt, defaultDir) + input, _ := reader.ReadString('\n') + input = strings.TrimSpace(input) + + if input == "" { + input = defaultDir + } + + // Check if directory exists + if _, err := os.Stat(input); os.IsNotExist(err) { + fmt.Printf("Directory '%s' doesn't exist. Create it? (Y/n): ", input) + response, _ := reader.ReadString('\n') + response = strings.TrimSpace(strings.ToLower(response)) + if response == "n" || response == "no" { + continue + } + + if err := os.MkdirAll(input, 0755); err != nil { + printError(fmt.Sprintf("Failed to create directory '%s': %v", input, err)) + continue + } + } + + // Check if directory is writable + if info, err := os.Stat(input); err == nil { + if info.Mode()&0200 == 0 { + printError(fmt.Sprintf("Directory '%s' is not writable", input)) + continue + } + } + + return input + } +} + +// WireGuard key generation +func generateWireGuardKeys() (string, string, error) { + // Generate private key + privateKeyBytes := make([]byte, 32) + if _, err := rand.Read(privateKeyBytes); err != nil { + return "", "", err + } + + // Ensure the key is valid for curve25519 + privateKeyBytes[0] &= 248 + privateKeyBytes[31] &= 127 + privateKeyBytes[31] |= 64 + + // Generate public key + var publicKeyBytes [32]byte + curve25519.ScalarBaseMult(&publicKeyBytes, (*[32]byte)(privateKeyBytes)) + + privateKey := base64.StdEncoding.EncodeToString(privateKeyBytes[:]) + publicKey := base64.StdEncoding.EncodeToString(publicKeyBytes[:]) + + return privateKey, publicKey, nil +} + +// Configuration generation +func generateConfig(hostname, ipAddress, privateKey, routingMode string) string { + var config strings.Builder + + // Interface section + config.WriteString("[Interface]\n") + config.WriteString(fmt.Sprintf("Address = %s/24\n", ipAddress)) + config.WriteString(fmt.Sprintf("PrivateKey = %s\n", privateKey)) + + // Add ListenPort for static servers + if port, exists := staticServers[ipAddress]; exists { + config.WriteString(fmt.Sprintf("ListenPort = %s\n", port)) + } + + // Add DNS for full tunnel mode + if routingMode == "full_tunnel" { + config.WriteString("DNS = 1.1.1.1, 8.8.8.8\n") + } + + // Zion peer (always enabled) + config.WriteString("\n#Zion peer (central server) - for access to entire network\n") + config.WriteString("[Peer]\n") + config.WriteString(fmt.Sprintf("PublicKey = %s\n", peers["zion"].PublicKey)) + + // Set AllowedIPs based on routing mode + if routingMode == "full_tunnel" { + config.WriteString("AllowedIPs = 0.0.0.0/0, ::/0\n") + } else { + config.WriteString("AllowedIPs = 10.8.0.0/24\n") + } + + config.WriteString(fmt.Sprintf("Endpoint = %s\n", peers["zion"].Endpoint)) + config.WriteString(fmt.Sprintf("PersistentKeepalive = %d\n", peers["zion"].Keepalive)) + + // Optional peers (commented out) + config.WriteString("\n#Cthulhu (optional if port 53 is also forwarded to bypass firewalls)\n") + config.WriteString("#[Peer]\n") + config.WriteString(fmt.Sprintf("#PublicKey = %s\n", peers["cthulhu"].PublicKey)) + config.WriteString(fmt.Sprintf("#AllowedIPs = %s\n", peers["cthulhu"].AllowedIPs)) + config.WriteString(fmt.Sprintf("#Endpoint = %s\n", peers["cthulhu"].Endpoint)) + config.WriteString(fmt.Sprintf("#PersistentKeepalive = %d\n", peers["cthulhu"].Keepalive)) + + config.WriteString("\n#Galaxy (located in Europe, NL)\n") + config.WriteString("#[Peer]\n") + config.WriteString(fmt.Sprintf("#PublicKey = %s\n", peers["galaxy"].PublicKey)) + config.WriteString(fmt.Sprintf("#AllowedIPs = %s\n", peers["galaxy"].AllowedIPs)) + config.WriteString(fmt.Sprintf("#Endpoint = %s\n", peers["galaxy"].Endpoint)) + config.WriteString(fmt.Sprintf("#PersistentKeepalive = %d\n", peers["galaxy"].Keepalive)) + + return config.String() +} + +// Check dependencies +func checkDependencies() error { + deps := []string{"wg", "wg-quick"} + + for _, dep := range deps { + if _, err := exec.LookPath(dep); err != nil { + return fmt.Errorf("missing dependency: %s", dep) + } + } + + return nil +} + +// Check if running as root +func isRunningAsRoot() bool { + return os.Geteuid() == 0 +} + +// Main function +func main() { + printHeader() + + // Check if running as root + runningAsRoot := isRunningAsRoot() + + var wgDir string + if runningAsRoot { + wgDir = "/etc/wireguard" + printStatus("Running as root - using system directories") + printStatus(fmt.Sprintf("WireGuard directory: %s", wgDir)) + } else { + wgDir = "." + printWarning("Not running as root - using current directory") + printStatus(fmt.Sprintf("WireGuard directory: %s", wgDir)) + printWarning("You'll need to manually copy config files to /etc/wireguard/ later") + } + fmt.Println() + + // Check dependencies + if err := checkDependencies(); err != nil { + printError(err.Error()) + printStatus("Install with: apt install wireguard-tools") + os.Exit(1) + } + + // Get directory for non-root users + if !runningAsRoot { + fmt.Printf("%sStep 1: Directory Selection%s\n", Blue, Reset) + fmt.Println() + printStatus("Choose where to save WireGuard files:") + fmt.Printf(" - Current directory: %s\n", wgDir) + fmt.Printf(" - Home directory: %s\n", os.Getenv("HOME")) + fmt.Println(" - Custom directory") + fmt.Println() + wgDir = getDirectoryInput("Enter directory path for WireGuard files", wgDir) + fmt.Println() + } + + // Get hostname + stepNum := "2" + if !runningAsRoot { + stepNum = "2" + } else { + stepNum = "1" + } + fmt.Printf("%sStep %s: Node Information%s\n", Blue, stepNum, Reset) + fmt.Println() + + hostname := getUserInput("Enter hostname for this node: ", validateHostname) + + // Get IP address + fmt.Println() + fmt.Println("Available IP ranges:") + fmt.Println(" - 10.8.0.x (recommended for NextGen network)") + fmt.Println(" - 10.0.0.x (alternative range)") + fmt.Println() + + ipAddress := getUserInput("Enter IP address for this node (e.g., 10.8.0.30): ", validateIP) + + // Get interface name + fmt.Println() + fmt.Println("Interface name options:") + fmt.Println(" - wg0 (default, most common)") + fmt.Println(" - wg1, wg2, etc. (if wg0 is already in use)") + fmt.Println(" - Custom name (e.g., nextgen, vpn, etc.)") + fmt.Println() + + interfaceName := getUserInput("Enter interface name (default: wg0): ", validateInterfaceName) + if interfaceName == "" { + interfaceName = "wg0" + } + + // Configuration options + fmt.Println() + if !runningAsRoot { + stepNum = "3" + } else { + stepNum = "2" + } + fmt.Printf("%sStep %s: Configuration Options%s\n", Blue, stepNum, Reset) + fmt.Println() + fmt.Println("Choose an option:") + fmt.Println("1. Generate keys only (manual config creation)") + fmt.Println("2. Generate keys + complete config (recommended)") + fmt.Println() + + reader := bufio.NewReader(os.Stdin) + var configChoice string + for { + fmt.Print("Enter your choice (1 or 2): ") + configChoice, _ = reader.ReadString('\n') + configChoice = strings.TrimSpace(configChoice) + if configChoice == "1" || configChoice == "2" { + break + } + printError("Invalid choice. Please enter 1 or 2.") + } + + // Traffic routing options + var routingMode string + if configChoice == "2" { + fmt.Println() + fmt.Println("Traffic routing options:") + fmt.Println("1. WireGuard traffic only (10.8.0.x network only)") + fmt.Println("2. All traffic through VPN (full tunnel)") + fmt.Println() + fmt.Println("Note: Full tunnel routes ALL internet traffic through the VPN.") + fmt.Println(" WireGuard-only keeps your regular internet traffic separate.") + fmt.Println() + + var routingChoice string + for { + fmt.Print("Enter your choice (1 or 2): ") + routingChoice, _ = reader.ReadString('\n') + routingChoice = strings.TrimSpace(routingChoice) + if routingChoice == "1" || routingChoice == "2" { + break + } + printError("Invalid choice. Please enter 1 or 2.") + } + + if routingChoice == "1" { + routingMode = "wg_only" + printStatus("Selected: WireGuard traffic only") + } else { + routingMode = "full_tunnel" + printStatus("Selected: All traffic through VPN") + } + } + + printStatus(fmt.Sprintf("Starting setup for %s (%s)...", hostname, ipAddress)) + fmt.Println() + + // Create directories + if err := os.MkdirAll(wgDir, 0755); err != nil { + printError(fmt.Sprintf("Failed to create directory: %v", err)) + os.Exit(1) + } + + // Generate keys + printStatus("Generating WireGuard keys...") + privateKey, publicKey, err := generateWireGuardKeys() + if err != nil { + printError(fmt.Sprintf("Failed to generate keys: %v", err)) + os.Exit(1) + } + + // Save keys + privateKeyFile := filepath.Join(wgDir, fmt.Sprintf("%s_private.key", hostname)) + publicKeyFile := filepath.Join(wgDir, fmt.Sprintf("%s_public.key", hostname)) + + if err := ioutil.WriteFile(privateKeyFile, []byte(privateKey), 0600); err != nil { + printError(fmt.Sprintf("Failed to save private key: %v", err)) + os.Exit(1) + } + + if err := ioutil.WriteFile(publicKeyFile, []byte(publicKey), 0600); err != nil { + printError(fmt.Sprintf("Failed to save public key: %v", err)) + os.Exit(1) + } + + printStatus("Keys generated successfully!") + fmt.Printf(" Private key: %s\n", privateKeyFile) + fmt.Printf(" Public key: %s\n", publicKeyFile) + fmt.Println() + + // Display node information + if !runningAsRoot { + stepNum = "4" + } else { + stepNum = "3" + } + fmt.Printf("%sStep %s: Node Information%s\n", Blue, stepNum, Reset) + fmt.Println("==========================================") + fmt.Printf("HOSTNAME: %s\n", hostname) + fmt.Printf("IP ADDRESS: %s\n", ipAddress) + fmt.Printf("PRIVATE KEY: %s\n", privateKey) + fmt.Printf("PUBLIC KEY: %s\n", publicKey) + fmt.Println("==========================================") + fmt.Println() + + // Save structured info + infoFile := filepath.Join(wgDir, fmt.Sprintf("%s_wg_info.json", hostname)) + if runningAsRoot { + infoFile = filepath.Join("/tmp", fmt.Sprintf("%s_wg_info.json", hostname)) + } + + wgConfig := WGConfig{ + Hostname: hostname, + IPAddress: ipAddress, + PrivateKey: privateKey, + PublicKey: publicKey, + RoutingMode: routingMode, + Generated: time.Now().Format(time.RFC3339), + ScriptVer: "2.2", + RunningRoot: runningAsRoot, + } + + infoData, err := json.MarshalIndent(wgConfig, "", " ") + if err != nil { + printError(fmt.Sprintf("Failed to marshal config: %v", err)) + os.Exit(1) + } + + if err := ioutil.WriteFile(infoFile, infoData, 0600); err != nil { + printError(fmt.Sprintf("Failed to save info file: %v", err)) + os.Exit(1) + } + + printStatus(fmt.Sprintf("Information saved to: %s", infoFile)) + fmt.Println() + + // Generate complete config if requested + if configChoice == "2" { + configFile := filepath.Join(wgDir, fmt.Sprintf("%s.conf", interfaceName)) + + printStatus(fmt.Sprintf("Generating complete %s.conf...", interfaceName)) + configContent := generateConfig(hostname, ipAddress, privateKey, routingMode) + + if err := ioutil.WriteFile(configFile, []byte(configContent), 0600); err != nil { + printError(fmt.Sprintf("Failed to write config file: %v", err)) + os.Exit(1) + } + + printStatus(fmt.Sprintf("Config written to: %s", configFile)) + if runningAsRoot { + printStatus("Permissions set to 600") + } + fmt.Println() + + // Next steps + if !runningAsRoot { + stepNum = "5" + } else { + stepNum = "4" + } + fmt.Printf("%sStep %s: Next Steps%s\n", Blue, stepNum, Reset) + fmt.Println() + + if runningAsRoot { + printStatus("Ready to start WireGuard:") + fmt.Printf(" systemctl enable --now wg-quick@%s\n", interfaceName) + } else { + printWarning("To enable WireGuard (requires root):") + fmt.Printf(" sudo cp %s /etc/wireguard/\n", configFile) + fmt.Printf(" sudo chmod 600 /etc/wireguard/%s.conf\n", interfaceName) + fmt.Printf(" sudo systemctl enable --now wg-quick@%s\n", interfaceName) + } + fmt.Println() + printWarning("IMPORTANT: Update other nodes with this peer info:") + fmt.Printf(" PublicKey = %s\n", publicKey) + fmt.Printf(" AllowedIPs = %s/32\n", ipAddress) + fmt.Println() + + // Config preview + fmt.Printf("%sConfig Preview:%s\n", Blue, Reset) + fmt.Println("----------------------------------------") + lines := strings.Split(configContent, "\n") + for i, line := range lines { + if i >= 5 { + break + } + fmt.Println(line) + } + fmt.Printf(" [... and %d total lines]\n", len(lines)) + fmt.Println("----------------------------------------") + fmt.Println() + + // Full tunnel instructions + if routingMode == "full_tunnel" { + fmt.Println() + printWarning("FULL TUNNEL MODE DETECTED - Endpoint Changes Required:") + fmt.Println() + fmt.Println("Since this node will route ALL traffic through the VPN, you need to:") + fmt.Println() + fmt.Println("1. Update Zion's config (/etc/wireguard/wg0.conf) to allow this peer:") + fmt.Println(" - Add the peer section as shown above") + fmt.Println(" - Ensure Zion has proper iptables rules for NAT/masquerading") + fmt.Println() + fmt.Println("2. Check Zion's iptables rules (run on Zion server):") + fmt.Println(" sudo iptables -t nat -L POSTROUTING") + fmt.Println(" sudo iptables -L FORWARD") + fmt.Println() + fmt.Println("3. If Zion doesn't have proper NAT rules, add them:") + fmt.Println(" sudo iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE") + fmt.Println(" sudo iptables -A FORWARD -i wg0 -j ACCEPT") + fmt.Println(" sudo iptables -A FORWARD -o wg0 -j ACCEPT") + fmt.Println() + fmt.Println("4. Enable IP forwarding on Zion (if not already enabled):") + fmt.Println(" echo 'net.ipv4.ip_forward=1' | sudo tee -a /etc/sysctl.conf") + fmt.Println(" sudo sysctl -p") + fmt.Println() + } + + // Zion update instructions + fmt.Printf("%s========================================%s\n", Red, Reset) + fmt.Printf("%s !!! NOW UPDATE ZION SERVER !!! %s\n", Red, Reset) + fmt.Printf("%s========================================%s\n", Red, Reset) + fmt.Println() + printWarning("You MUST add this peer to Zion's config (/etc/wireguard/wg0.conf):") + fmt.Println() + fmt.Printf("%s#%s%s\n", Yellow, hostname, Reset) + fmt.Printf("%s[Peer]%s\n", Yellow, Reset) + fmt.Printf("%sPublicKey = %s%s\n", Yellow, publicKey, Reset) + fmt.Printf("%sAllowedIPs = %s/32%s\n", Yellow, ipAddress, Reset) + fmt.Println() + printWarning("After updating Zion, restart its WireGuard:") + fmt.Println(" systemctl restart wg-quick@wg0") + fmt.Println() + + if runningAsRoot { + printWarning("Then restart this node's WireGuard:") + fmt.Printf(" systemctl restart wg-quick@%s\n", interfaceName) + } else { + printWarning("Then restart this node's WireGuard:") + fmt.Printf(" sudo systemctl restart wg-quick@%s\n", interfaceName) + } + } else { + // Manual config generation path + if !runningAsRoot { + stepNum = "5" + } else { + stepNum = "4" + } + fmt.Printf("%sStep %s: Next Steps%s\n", Blue, stepNum, Reset) + fmt.Println() + printStatus("Keys generated successfully!") + fmt.Printf(" Private key: %s\n", privateKeyFile) + fmt.Printf(" Public key: %s\n", publicKeyFile) + fmt.Println() + printWarning("Next steps:") + fmt.Printf(" - Create %s.conf manually using the keys above\n", interfaceName) + fmt.Printf(" - Copy config to %s\n", filepath.Join(wgDir, fmt.Sprintf("%s.conf", interfaceName))) + if runningAsRoot { + fmt.Printf(" - Set permissions: chmod 600 %s\n", filepath.Join(wgDir, fmt.Sprintf("%s.conf", interfaceName))) + fmt.Printf(" - Enable/start: systemctl enable --now wg-quick@%s\n", interfaceName) + } else { + fmt.Printf(" - Copy to system: sudo cp %s /etc/wireguard/\n", filepath.Join(wgDir, fmt.Sprintf("%s.conf", interfaceName))) + fmt.Printf(" - Set permissions: sudo chmod 600 /etc/wireguard/%s.conf\n", interfaceName) + fmt.Printf(" - Enable/start: sudo systemctl enable --now wg-quick@%s\n", interfaceName) + } + fmt.Println() + fmt.Printf("%s========================================%s\n", Red, Reset) + fmt.Printf("%s !!! NOW UPDATE ZION SERVER !!! %s\n", Red, Reset) + fmt.Printf("%s========================================%s\n", Red, Reset) + fmt.Println() + printWarning("You MUST add this peer to Zion's config (/etc/wireguard/wg0.conf):") + fmt.Println() + fmt.Printf("%s#%s%s\n", Yellow, hostname, Reset) + fmt.Printf("%s[Peer]%s\n", Yellow, Reset) + fmt.Printf("%sPublicKey = %s%s\n", Yellow, publicKey, Reset) + fmt.Printf("%sAllowedIPs = %s/32%s\n", Yellow, ipAddress, Reset) + fmt.Println() + printWarning("After updating Zion, restart its WireGuard:") + fmt.Println(" systemctl restart wg-quick@wg0") + fmt.Println() + + if runningAsRoot { + printWarning("Then restart this node's WireGuard:") + fmt.Printf(" systemctl restart wg-quick@%s\n", interfaceName) + } else { + printWarning("Then restart this node's WireGuard:") + fmt.Printf(" sudo systemctl restart wg-quick@%s\n", interfaceName) + } + } + + fmt.Println() + printStatus(fmt.Sprintf("Setup complete for %s!", hostname)) + fmt.Println() +} diff --git a/scripting/wgez-setup.sh b/scripting/wgez-setup.sh new file mode 100644 index 0000000..8653aae --- /dev/null +++ b/scripting/wgez-setup.sh @@ -0,0 +1,739 @@ +#!/bin/bash + +# NextGen WireGuard Easy Setup Script (wgez-setup.sh) +# Interactive setup script for new WireGuard nodes + +set -euo pipefail + +# Colors for better UX +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +# Globals +SCRIPT_DIR="${BASH_SOURCE[0]%/*}" +SCRIPT_DIR="$(cd "$SCRIPT_DIR" && pwd)" + +# Determine if running as root and set appropriate directories +if [[ $EUID -eq 0 ]]; then + WG_DIR="/etc/wireguard" + BACKUP_DIR="$WG_DIR/backups" + RUNNING_AS_ROOT=true +else + WG_DIR="$(pwd)" + BACKUP_DIR="$WG_DIR/backups" + RUNNING_AS_ROOT=false +fi + +# Logging +log_file="/var/log/wgez-setup.log" +if [[ "$RUNNING_AS_ROOT" == "false" ]]; then + log_file="$WG_DIR/wgez-setup.log" +fi +exec 19> >(exec cat >&2) +exec 20> >(exec tee -a "$log_file" >&2) +BASH_XTRACEFD=20 + +print_status() { + echo -e "${GREEN}[INFO]${NC} $1" | tee -a "$log_file" +} + +print_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" | tee -a "$log_file" +} + +print_error() { + echo -e "${RED}[ERROR]${NC} $1" | tee -a "$log_file" +} + +print_header() { + echo -e "${BLUE}================================${NC}" + echo -e "${BLUE} NextGen WireGuard Easy Setup ${NC}" + echo -e "${BLUE}================================${NC}" + echo "" +} + +cleanup() { + local exit_code=$? + if [[ $exit_code -ne 0 ]]; then + print_error "Script failed with exit code $exit_code" + if [[ -f "/tmp/${HOSTNAME:-unknown}_wg_info.json" ]]; then + rm -f "/tmp/${HOSTNAME}_wg_info.json" + fi + fi + exit $exit_code +} + +# Enhanced validation functions +validate_hostname() { + local hostname="$1" + if [[ -z "$hostname" ]]; then + print_error "Hostname cannot be empty" + return 1 + fi + if [[ ! "$hostname" =~ ^[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]$|^[a-zA-Z0-9]$ ]]; then + print_error "Invalid hostname format. Use alphanumeric characters and hyphens only." + return 1 + fi + if [[ ${#hostname} -gt 63 ]]; then + print_error "Hostname too long (max 63 characters)" + return 1 + fi + return 0 +} + +validate_ip() { + local ip="$1" + local octets + + if [[ -z "$ip" ]]; then + print_error "IP address cannot be empty" + return 1 + fi + + IFS='.' read -ra octets <<< "$ip" + if [[ ${#octets[@]} -ne 4 ]]; then + print_error "Invalid IP format" + return 1 + fi + + for octet in "${octets[@]}"; do + if ! [[ "$octet" =~ ^[0-9]+$ ]] || [[ $octet -gt 255 ]] || [[ $octet -lt 0 ]]; then + print_error "Invalid IP octet: $octet" + return 1 + fi + done + + if [[ ! "$ip" =~ ^10\.(8|0)\.0\.[0-9]{1,3}$ ]]; then + print_warning "IP should be in 10.8.0.x or 10.0.0.x range for NextGen network" + return 2 + fi + + return 0 +} + +validate_interface_name() { + local interface="$1" + + if [[ -z "$interface" ]]; then + print_error "Interface name cannot be empty" + return 1 + fi + + if [[ ! "$interface" =~ ^[a-zA-Z][a-zA-Z0-9]*$ ]]; then + print_error "Invalid interface name. Use letters and numbers only, starting with a letter." + return 1 + fi + + if [[ ${#interface} -gt 15 ]]; then + print_error "Interface name too long (max 15 characters)" + return 1 + fi + + # Check if interface already exists (only if running as root) + if [[ "$RUNNING_AS_ROOT" == "true" ]] && ip link show "$interface" &>/dev/null; then + print_warning "Interface '$interface' already exists" + return 2 + fi + + # Check if config file already exists + if [[ -f "$WG_DIR/${interface}.conf" ]]; then + print_warning "Config file '$WG_DIR/${interface}.conf' already exists" + return 2 + fi + + return 0 +} + +check_ip_conflict() { + local ip="$1" + local config_file="$2" + + # Check if IP is already in use on network + if ping -c1 -W1 "$ip" &>/dev/null; then + print_warning "IP $ip appears to be in use on the network" + return 1 + fi + + # Check existing WireGuard configs + if [[ -f "$config_file" ]] && grep -q "Address = $ip" "$config_file"; then + print_warning "IP $ip already configured in existing WireGuard config" + return 1 + fi + + return 0 +} + +check_dependencies() { + local deps=("wg" "wg-quick") + local missing=() + + for dep in "${deps[@]}"; do + if ! command -v "$dep" &>/dev/null; then + missing+=("$dep") + fi + done + + if [[ ${#missing[@]} -gt 0 ]]; then + print_error "Missing dependencies: ${missing[*]}" + print_status "Install with: apt install wireguard-tools" + return 1 + fi + + # Only check systemctl if running as root + if [[ "$RUNNING_AS_ROOT" == "true" ]] && ! command -v "systemctl" &>/dev/null; then + print_error "Missing dependency: systemctl" + print_status "Install with: apt install systemd" + return 1 + fi + + return 0 +} + +backup_existing_config() { + local config_file="$1" + if [[ -f "$config_file" ]]; then + mkdir -p "$BACKUP_DIR" + local config_name=$(basename "$config_file") + local backup_file="$BACKUP_DIR/${config_name}.backup.$(date +%Y%m%d_%H%M%S)" + cp "$config_file" "$backup_file" + print_status "Existing config backed up to: $backup_file" + fi +} + +get_user_input() { + local prompt="$1" + local var_name="$2" + local validator="$3" + local value + + while true; do + read -p "$prompt" value + if $validator "$value"; then + eval "$var_name='$value'" + break + elif [[ $? -eq 2 ]]; then + read -p "Continue anyway? (y/N): " -n 1 -r + echo + if [[ $REPLY =~ ^[Yy]$ ]]; then + eval "$var_name='$value'" + break + fi + fi + done +} + +get_directory_input() { + local prompt="$1" + local var_name="$2" + local default_dir="$3" + local directory + + while true; do + read -p "$prompt [$default_dir]: " directory + if [[ -z "$directory" ]]; then + directory="$default_dir" + fi + + # Create directory if it doesn't exist + if [[ ! -d "$directory" ]]; then + read -p "Directory '$directory' doesn't exist. Create it? (Y/n): " -n 1 -r + echo + if [[ $REPLY =~ ^[Nn]$ ]]; then + continue + fi + if ! mkdir -p "$directory" 2>/dev/null; then + print_error "Failed to create directory '$directory'" + continue + fi + fi + + # Check if directory is writable + if [[ ! -w "$directory" ]]; then + print_error "Directory '$directory' is not writable" + continue + fi + + eval "$var_name='$directory'" + break + done +} + +generate_config() { + local hostname="$1" + local ip_address="$2" + local private_key="$3" + local routing_mode="${4:-wg_only}" + + # Define static servers + local -A static_servers=( + ["10.8.0.1"]="51820" + ["10.8.0.10"]="53535" + ["10.8.0.99"]="54382" + ) + + local config_content="[Interface] +Address = $ip_address/24 +PrivateKey = $private_key" + + # Add ListenPort for static servers + local is_static_server=false + if [[ -n "${static_servers[$ip_address]:-}" ]]; then + is_static_server=true + config_content="$config_content +ListenPort = ${static_servers[$ip_address]}" + fi + + # Add DNS for full tunnel mode + if [[ "$routing_mode" == "full_tunnel" ]]; then + config_content="$config_content +DNS = 1.1.1.1, 8.8.8.8" + fi + + config_content="$config_content + +#Zion peer (central server) - for access to entire network +[Peer] +PublicKey = 2ztJbrN1x1NWanzPGLiKL19ZkdOhm5Y7WeKEWBT5cyg=" + + # Set AllowedIPs based on routing mode + if [[ "$routing_mode" == "full_tunnel" ]]; then + config_content="$config_content +AllowedIPs = 0.0.0.0/0, ::/0" + else + config_content="$config_content +AllowedIPs = 10.8.0.0/24" + fi + + config_content="$config_content +Endpoint = ugh.im:51820" + + # Set keepalive value (25 for all peers as per new configuration) + config_content="$config_content +PersistentKeepalive = 25" + + config_content="$config_content + +#Cthulhu (optional if port 53 is also forwarded to bypass firewalls) +#[Peer] +#PublicKey = NBktXKy1s0n2lIlIMODvOqKNwAtYdoZH5feKt5P43i0= +#AllowedIPs = 10.8.0.10/32 +#Endpoint = aw2cd67.glddns.com:53535" + + config_content="$config_content +#PersistentKeepalive = 25" + + config_content="$config_content + +#Galaxy (located in Europe, NL) +#[Peer] +#PublicKey = QBNt00VSedxPlq3ZvsdYaqIcbudCAyxv9TG65aPVZzM= +#AllowedIPs = 10.8.0.99/32 +#Endpoint = galaxyspin.space:54382" + + config_content="$config_content +#PersistentKeepalive = 25" + + echo "$config_content" +} + +print_progress() { + local step="$1" + local total="$2" + echo -e "${BLUE}[$step/$total]${NC} $3" +} + +validate_config() { + local config_file="$1" + if ! wg-quick strip "$config_file" >/dev/null 2>&1; then + print_error "Generated config has syntax errors" + return 1 + fi + return 0 +} + +check_network_connectivity() { + if ! ping -c1 -W3 8.8.8.8 &>/dev/null; then + print_warning "No internet connectivity detected" + return 1 + fi + return 0 +} + +main() { + trap cleanup EXIT + + print_header + + # Check if running as root and inform user + if [[ "$RUNNING_AS_ROOT" == "true" ]]; then + print_status "Running as root - using system directories" + print_status "WireGuard directory: $WG_DIR" + else + print_warning "Not running as root - using current directory" + print_status "WireGuard directory: $WG_DIR" + print_warning "You'll need to manually copy config files to /etc/wireguard/ later" + fi + echo "" + + # Check dependencies + if ! check_dependencies; then + exit 1 + fi + + # Get directory for non-root users + if [[ "$RUNNING_AS_ROOT" == "false" ]]; then + echo -e "${BLUE}Step 1: Directory Selection${NC}" + echo "" + print_status "Choose where to save WireGuard files:" + echo " - Current directory: $(pwd)" + echo " - Home directory: $HOME" + echo " - Custom directory" + echo "" + get_directory_input "Enter directory path for WireGuard files" WG_DIR "$(pwd)" + BACKUP_DIR="$WG_DIR/backups" + echo "" + fi + + # Get hostname + echo -e "${BLUE}Step $([[ "$RUNNING_AS_ROOT" == "false" ]] && echo "2" || echo "1"): Node Information${NC}" + echo "" + + get_user_input "Enter hostname for this node: " HOSTNAME validate_hostname + + # Get IP address + echo "" + echo "Available IP ranges:" + echo " - 10.8.0.x (recommended for NextGen network)" + echo " - 10.0.0.x (alternative range)" + echo "" + + get_user_input "Enter IP address for this node (e.g., 10.8.0.30): " IP_ADDRESS validate_ip + + # Get interface name + echo "" + echo "Interface name options:" + echo " - wg0 (default, most common)" + echo " - wg1, wg2, etc. (if wg0 is already in use)" + echo " - Custom name (e.g., nextgen, vpn, etc.)" + echo "" + + get_user_input "Enter interface name (default: wg0): " INTERFACE_NAME validate_interface_name + if [[ -z "$INTERFACE_NAME" ]]; then + INTERFACE_NAME="wg0" + fi + + # Set config file path + CONFIG_FILE="$WG_DIR/${INTERFACE_NAME}.conf" + + # Check for IP conflicts + if ! check_ip_conflict "$IP_ADDRESS" "$CONFIG_FILE"; then + read -p "Continue anyway? (y/N): " -n 1 -r + echo + [[ ! $REPLY =~ ^[Yy]$ ]] && exit 1 + fi + + # Check if keys already exist + if [[ -f "$WG_DIR/${HOSTNAME}_private.key" ]]; then + print_warning "Keys for $HOSTNAME already exist!" + read -p "Overwrite? (y/N): " -n 1 -r + echo + [[ ! $REPLY =~ ^[Yy]$ ]] && exit 1 + fi + + # Configuration options + echo "" + echo -e "${BLUE}Step $([[ "$RUNNING_AS_ROOT" == "false" ]] && echo "3" || echo "2"): Configuration Options${NC}" + echo "" + echo "Choose an option:" + echo "1. Generate keys only (manual config creation)" + echo "2. Generate keys + complete ${INTERFACE_NAME}.conf (recommended)" + echo "" + + while true; do + read -p "Enter your choice (1 or 2): " CONFIG_CHOICE + if [[ "$CONFIG_CHOICE" == "1" || "$CONFIG_CHOICE" == "2" ]]; then + break + fi + print_error "Invalid choice. Please enter 1 or 2." + done + + # Traffic routing options (only if generating complete config) + if [[ "$CONFIG_CHOICE" == "2" ]]; then + echo "" + echo "Traffic routing options:" + echo "1. WireGuard traffic only (10.8.0.x network only)" + echo "2. All traffic through VPN (full tunnel)" + echo "" + echo "Note: Full tunnel routes ALL internet traffic through the VPN." + echo " WireGuard-only keeps your regular internet traffic separate." + echo "" + + while true; do + read -p "Enter your choice (1 or 2): " ROUTING_CHOICE + if [[ "$ROUTING_CHOICE" == "1" || "$ROUTING_CHOICE" == "2" ]]; then + break + fi + print_error "Invalid choice. Please enter 1 or 2." + done + + if [[ "$ROUTING_CHOICE" == "1" ]]; then + ROUTING_MODE="wg_only" + print_status "Selected: WireGuard traffic only" + else + ROUTING_MODE="full_tunnel" + print_status "Selected: All traffic through VPN" + fi + fi + + print_status "Starting setup for $HOSTNAME ($IP_ADDRESS)..." + echo "" + + # Create directories + mkdir -p "$WG_DIR" "$BACKUP_DIR" + cd "$WG_DIR" + + # Set secure permissions (only if running as root) + if [[ "$RUNNING_AS_ROOT" == "true" ]]; then + umask 077 + fi + + print_status "Generating WireGuard keys..." + PRIVATE_KEY=$(wg genkey) + PUBLIC_KEY=$(echo "$PRIVATE_KEY" | wg pubkey) + + # Save keys + printf "%s\n%s\n" "$PRIVATE_KEY" "$PUBLIC_KEY" > "${HOSTNAME}_keys.tmp" + mv "${HOSTNAME}_keys.tmp" "${HOSTNAME}_private.key" + echo "$PUBLIC_KEY" > "${HOSTNAME}_public.key" + + # Set permissions (only if running as root) + if [[ "$RUNNING_AS_ROOT" == "true" ]]; then + chmod 600 "${HOSTNAME}_private.key" "${HOSTNAME}_public.key" + fi + + print_status "Keys generated successfully!" + echo " Private key: $WG_DIR/${HOSTNAME}_private.key" + echo " Public key: $WG_DIR/${HOSTNAME}_public.key" + echo "" + + echo -e "${BLUE}Step $([[ "$RUNNING_AS_ROOT" == "false" ]] && echo "4" || echo "3"): Node Information${NC}" + echo "==========================================" + echo "HOSTNAME: $HOSTNAME" + echo "IP ADDRESS: $IP_ADDRESS" + echo "PRIVATE KEY: $PRIVATE_KEY" + echo "PUBLIC KEY: $PUBLIC_KEY" + echo "==========================================" + echo "" + + # Save structured info + INFO_FILE="/tmp/${HOSTNAME}_wg_info.json" + if [[ "$RUNNING_AS_ROOT" == "false" ]]; then + INFO_FILE="$WG_DIR/${HOSTNAME}_wg_info.json" + fi + cat > "$INFO_FILE" << EOF +{ + "hostname": "$HOSTNAME", + "ip_address": "$IP_ADDRESS", + "private_key": "$PRIVATE_KEY", + "public_key": "$PUBLIC_KEY", + "routing_mode": "${ROUTING_MODE:-wg_only}", + "generated": "$(date -Iseconds)", + "script_version": "2.2", + "running_as_root": $RUNNING_AS_ROOT +} +EOF + + print_status "Information saved to: $INFO_FILE" + echo "" + + # Generate complete config if requested + if [[ "$CONFIG_CHOICE" == "2" ]]; then + backup_existing_config "$CONFIG_FILE" + + print_status "Generating complete ${INTERFACE_NAME}.conf..." + generate_config "$HOSTNAME" "$IP_ADDRESS" "$PRIVATE_KEY" "$ROUTING_MODE" > "$CONFIG_FILE" + + # Set permissions (only if running as root) + if [[ "$RUNNING_AS_ROOT" == "true" ]]; then + chmod 600 "$CONFIG_FILE" + fi + + print_status "Config written to: $CONFIG_FILE" + if [[ "$RUNNING_AS_ROOT" == "true" ]]; then + print_status "Permissions set to 600" + fi + echo "" + + echo -e "${BLUE}Step $([[ "$RUNNING_AS_ROOT" == "false" ]] && echo "5" || echo "4"): Next Steps${NC}" + echo "" + + if [[ "$RUNNING_AS_ROOT" == "true" ]]; then + print_status "Ready to start WireGuard:" + echo " systemctl enable --now wg-quick@${INTERFACE_NAME}" + else + print_warning "To enable WireGuard (requires root):" + echo " sudo cp $CONFIG_FILE /etc/wireguard/" + echo " sudo chmod 600 /etc/wireguard/${INTERFACE_NAME}.conf" + echo " sudo systemctl enable --now wg-quick@${INTERFACE_NAME}" + fi + echo "" + print_warning "IMPORTANT: Update other nodes with this peer info:" + echo " PublicKey = $PUBLIC_KEY" + echo " AllowedIPs = $IP_ADDRESS/32" + echo "" + + echo -e "${BLUE}Config Preview:${NC}" + echo "----------------------------------------" + head -5 "$CONFIG_FILE" + echo " [... and $(wc -l < "$CONFIG_FILE" | tr -d ' ') total lines]" + echo "----------------------------------------" + + echo "" + if [[ "$RUNNING_AS_ROOT" == "true" ]]; then + print_status "Configuration complete! To start WireGuard:" + echo " systemctl enable --now wg-quick@${INTERFACE_NAME}" + echo "" + print_status "To check status:" + echo " wg show ${INTERFACE_NAME}" + echo " systemctl status wg-quick@${INTERFACE_NAME}" + echo "" + print_status "To view logs:" + echo " journalctl -u wg-quick@${INTERFACE_NAME} -f" + else + print_status "Configuration complete! To enable WireGuard:" + echo " sudo cp $CONFIG_FILE /etc/wireguard/" + echo " sudo chmod 600 /etc/wireguard/${INTERFACE_NAME}.conf" + echo " sudo systemctl enable --now wg-quick@${INTERFACE_NAME}" + echo "" + print_status "To check status (requires root):" + echo " sudo wg show ${INTERFACE_NAME}" + echo " sudo systemctl status wg-quick@${INTERFACE_NAME}" + fi + echo "" + echo -e "${RED}========================================${NC}" + echo -e "${RED} !!! NOW UPDATE ZION SERVER !!! ${NC}" + echo -e "${RED}========================================${NC}" + echo "" + print_warning "You MUST add this peer to Zion's config (/etc/wireguard/wg0.conf):" + echo "" + echo -e "${YELLOW}#$HOSTNAME${NC}" + echo -e "${YELLOW}[Peer]${NC}" + echo -e "${YELLOW}PublicKey = $PUBLIC_KEY${NC}" + echo -e "${YELLOW}AllowedIPs = $IP_ADDRESS/32${NC}" + echo "" + print_warning "After updating Zion, restart its WireGuard:" + echo " systemctl restart wg-quick@wg0" + echo "" + + # Add endpoint-specific instructions for full tunnel mode + if [[ "$ROUTING_MODE" == "full_tunnel" ]]; then + echo "" + print_warning "FULL TUNNEL MODE DETECTED - Endpoint Changes Required:" + echo "" + echo "Since this node will route ALL traffic through the VPN, you need to:" + echo "" + echo "1. Update Zion's config (/etc/wireguard/wg0.conf) to allow this peer:" + echo " - Add the peer section as shown above" + echo " - Ensure Zion has proper iptables rules for NAT/masquerading" + echo "" + echo "2. Check Zion's iptables rules (run on Zion server):" + echo " sudo iptables -t nat -L POSTROUTING" + echo " sudo iptables -L FORWARD" + echo "" + echo "3. If Zion doesn't have proper NAT rules, add them:" + echo " sudo iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE" + echo " sudo iptables -A FORWARD -i wg0 -j ACCEPT" + echo " sudo iptables -A FORWARD -o wg0 -j ACCEPT" + echo "" + echo "4. Enable IP forwarding on Zion (if not already enabled):" + echo " echo 'net.ipv4.ip_forward=1' | sudo tee -a /etc/sysctl.conf" + echo " sudo sysctl -p" + echo "" + fi + + if [[ "$RUNNING_AS_ROOT" == "true" ]]; then + print_warning "Then restart this node's WireGuard:" + echo " systemctl restart wg-quick@${INTERFACE_NAME}" + else + print_warning "Then restart this node's WireGuard:" + echo " sudo systemctl restart wg-quick@${INTERFACE_NAME}" + fi + else + echo -e "${BLUE}Step $([[ "$RUNNING_AS_ROOT" == "false" ]] && echo "5" || echo "4"): Next Steps${NC}" + echo "" + print_status "Keys generated successfully!" + echo " Private key: $WG_DIR/${HOSTNAME}_private.key" + echo " Public key: $WG_DIR/${HOSTNAME}_public.key" + echo "" + print_warning "Next steps:" + echo " - Create ${INTERFACE_NAME}.conf manually using the keys above" + echo " - Copy config to $CONFIG_FILE" + if [[ "$RUNNING_AS_ROOT" == "true" ]]; then + echo " - Set permissions: chmod 600 $CONFIG_FILE" + echo " - Enable/start: systemctl enable --now wg-quick@${INTERFACE_NAME}" + else + echo " - Copy to system: sudo cp $CONFIG_FILE /etc/wireguard/" + echo " - Set permissions: sudo chmod 600 /etc/wireguard/${INTERFACE_NAME}.conf" + echo " - Enable/start: sudo systemctl enable --now wg-quick@${INTERFACE_NAME}" + fi + echo "" + echo -e "${RED}========================================${NC}" + echo -e "${RED} !!! NOW UPDATE ZION SERVER !!! ${NC}" + echo -e "${RED}========================================${NC}" + echo "" + print_warning "You MUST add this peer to Zion's config (/etc/wireguard/wg0.conf):" + echo "" + echo -e "${YELLOW}#$HOSTNAME${NC}" + echo -e "${YELLOW}[Peer]${NC}" + echo -e "${YELLOW}PublicKey = $PUBLIC_KEY${NC}" + echo -e "${YELLOW}AllowedIPs = $IP_ADDRESS/32${NC}" + echo "" + print_warning "After updating Zion, restart its WireGuard:" + echo " systemctl restart wg-quick@wg0" + echo "" + + # Add endpoint-specific instructions for full tunnel mode (manual path) + if [[ "${ROUTING_MODE:-wg_only}" == "full_tunnel" ]]; then + echo "" + print_warning "FULL TUNNEL MODE DETECTED - Endpoint Changes Required:" + echo "" + echo "Since this node will route ALL traffic through the VPN, you need to:" + echo "" + echo "1. Update Zion's config (/etc/wireguard/wg0.conf) to allow this peer:" + echo " - Add the peer section as shown above" + echo " - Ensure Zion has proper iptables rules for NAT/masquerading" + echo "" + echo "2. Check Zion's iptables rules (run on Zion server):" + echo " sudo iptables -t nat -L POSTROUTING" + echo " sudo iptables -L FORWARD" + echo "" + echo "3. If Zion doesn't have proper NAT rules, add them:" + echo " sudo iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE" + echo " sudo iptables -A FORWARD -i wg0 -j ACCEPT" + echo " sudo iptables -A FORWARD -o wg0 -j ACCEPT" + echo "" + echo "4. Enable IP forwarding on Zion (if not already enabled):" + echo " echo 'net.ipv4.ip_forward=1' | sudo tee -a /etc/sysctl.conf" + echo " sudo sysctl -p" + echo "" + fi + + if [[ "$RUNNING_AS_ROOT" == "true" ]]; then + print_warning "Then restart this node's WireGuard:" + echo " systemctl restart wg-quick@${INTERFACE_NAME}" + else + print_warning "Then restart this node's WireGuard:" + echo " sudo systemctl restart wg-quick@${INTERFACE_NAME}" + fi + fi + + echo "" + print_status "Setup complete for $HOSTNAME!" + print_status "Log file available at: $log_file" + echo "" +} + +main "$@" \ No newline at end of file diff --git a/test_overwrite_protection.sh b/test_overwrite_protection.sh new file mode 100755 index 0000000..0c38300 --- /dev/null +++ b/test_overwrite_protection.sh @@ -0,0 +1,74 @@ +#!/bin/bash + +# Test script to demonstrate file overwrite protection + +set -e + +# Colors for output +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +print_status() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +print_header() { + echo -e "${BLUE}================================${NC}" + echo -e "${BLUE}$1${NC}" + echo -e "${BLUE}================================${NC}" +} + +print_header "Testing File Overwrite Protection" + +echo "This test will demonstrate the file overwrite protection feature." +echo "" + +# Create a test configuration file first +print_status "Creating a test configuration file..." +mkdir -p wireguard_configs +echo "# Test configuration" > wireguard_configs/test_node.conf +echo "This is a test file" >> wireguard_configs/test_node.conf + +print_status "Test file created: wireguard_configs/test_node.conf" +echo "" + +# Test 1: Try to create a configuration with the same name (should prompt for overwrite) +print_header "Test 1: Attempting to create configuration with existing name" + +echo "Now we'll try to create a configuration with the same name 'test_node'" +echo "The script should detect the existing file and ask if you want to overwrite it." +echo "" + +# Create test input that will trigger the overwrite prompt +cat > /tmp/test_overwrite_input.txt << 'EOF' +test_node +10.8.0.5/24 +n +n +n +EOF + +print_status "Running setup script..." +echo "When prompted, you can choose:" +echo " 'y' to overwrite the existing file" +echo " 'n' to cancel the operation" +echo "" + +# Run the setup script +./wireguard_setup.sh < /tmp/test_overwrite_input.txt + +# Clean up +rm -f /tmp/test_overwrite_input.txt + +print_status "Test completed!" +echo "" +echo "The script should have:" +echo "1. Detected the existing test_node.conf file" +echo "2. Asked if you wanted to overwrite it" +echo "3. Either overwritten it (if you chose 'y') or cancelled (if you chose 'n')" \ No newline at end of file diff --git a/test_zion_peer.sh b/test_zion_peer.sh new file mode 100755 index 0000000..972a930 --- /dev/null +++ b/test_zion_peer.sh @@ -0,0 +1,58 @@ +#!/bin/bash + +# Test script to demonstrate Zion default peer feature + +set -e + +# Colors for output +GREEN='\033[0;32m' +BLUE='\033[0;34m' +NC='\033[0m' + +print_status() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +print_header() { + echo -e "${BLUE}================================${NC}" + echo -e "${BLUE}$1${NC}" + echo -e "${BLUE}================================${NC}" +} + +print_header "Testing Zion Default Peer Feature" + +echo "This test will create a client configuration with Zion as the default peer." +echo "The script will automatically include Zion's connection details." +echo "" + +# Create test input for a client configuration +cat > /tmp/test_zion_input.txt << 'EOF' +test_client +10.8.0.5/24 +n +y +n +EOF + +print_status "Running setup script with test configuration..." +echo "Input will be:" +echo " Node name: test_client" +echo " IP: 10.8.0.5/24" +echo " Server mode: n (no)" +echo " Add Zion: y (yes)" +echo " Add additional peers: n (no)" +echo "" + +# Run the setup script +./wireguard_setup.sh < /tmp/test_zion_input.txt + +# Clean up +rm -f /tmp/test_zion_input.txt + +print_status "Test completed! Check wireguard_configs/test_client.conf" +echo "" +echo "The generated configuration should include Zion as a peer with:" +echo " Public Key: 2ztJbrN1x1NWanzPGLiKL19ZkdOhm5Y7WeKEWBT5cyg=" +echo " Endpoint: ugh.im:51820" +echo " Allowed IPs: 10.8.0.0/24" +echo " Persistent Keepalive: 25" \ No newline at end of file diff --git a/validate_config.sh b/validate_config.sh new file mode 100755 index 0000000..3534c69 --- /dev/null +++ b/validate_config.sh @@ -0,0 +1,295 @@ +#!/bin/bash + +# WireGuard Configuration Validator +# This script validates WireGuard configuration files for common issues + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +print_status() { + echo -e "${GREEN}[PASS]${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}[WARN]${NC} $1" +} + +print_error() { + echo -e "${RED}[FAIL]${NC} $1" +} + +print_header() { + echo -e "${BLUE}================================${NC}" + echo -e "${BLUE}$1${NC}" + echo -e "${BLUE}================================${NC}" +} + +# Function to validate IP address format +validate_ip() { + local ip=$1 + if [[ $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/[0-9]{1,2}$ ]]; then + return 0 + else + return 1 + fi +} + +# Function to validate port number +validate_port() { + local port=$1 + if [[ $port =~ ^[0-9]+$ ]] && [ $port -ge 1 ] && [ $port -le 65535 ]; then + return 0 + else + return 1 + fi + +} + +# Function to validate WireGuard key format +validate_wg_key() { + local key=$1 + # WireGuard keys are base64 encoded and typically 44 characters long + if [[ $key =~ ^[A-Za-z0-9+/]{43}=$ ]]; then + return 0 + else + return 1 + fi +} + +# Function to validate endpoint format +validate_endpoint() { + local endpoint=$1 + if [[ $endpoint =~ ^[a-zA-Z0-9.-]+:[0-9]+$ ]]; then + return 0 + else + return 1 + fi +} + +# Function to validate a single configuration file +validate_config_file() { + local config_file=$1 + local errors=0 + local warnings=0 + + print_header "Validating: $config_file" + + if [ ! -f "$config_file" ]; then + print_error "Configuration file not found: $config_file" + return 1 + fi + + # Check file permissions + local perms=$(stat -c %a "$config_file") + if [ "$perms" != "600" ]; then + print_warning "File permissions should be 600, current: $perms" + ((warnings++)) + fi + + # Parse the configuration file + local in_interface=0 + local in_peer=0 + local has_interface=0 + local has_private_key=0 + local has_address=0 + local peer_count=0 + + while IFS= read -r line; do + # Skip comments and empty lines + if [[ $line =~ ^[[:space:]]*# ]] || [[ -z "${line// }" ]]; then + continue + fi + + # Check for Interface section + if [[ $line =~ ^\[Interface\] ]]; then + in_interface=1 + in_peer=0 + has_interface=1 + continue + fi + + # Check for Peer section + if [[ $line =~ ^\[Peer\] ]]; then + in_interface=0 + in_peer=1 + ((peer_count++)) + continue + fi + + # Parse Interface section + if [ $in_interface -eq 1 ]; then + if [[ $line =~ ^PrivateKey[[:space:]]*=[[:space:]]*(.+)$ ]]; then + local private_key="${BASH_REMATCH[1]}" + if validate_wg_key "$private_key"; then + print_status "Valid private key found" + has_private_key=1 + else + print_error "Invalid private key format" + ((errors++)) + fi + elif [[ $line =~ ^Address[[:space:]]*=[[:space:]]*(.+)$ ]]; then + local address="${BASH_REMATCH[1]}" + if validate_ip "$address"; then + print_status "Valid address: $address" + has_address=1 + else + print_error "Invalid address format: $address" + ((errors++)) + fi + elif [[ $line =~ ^ListenPort[[:space:]]*=[[:space:]]*(.+)$ ]]; then + local port="${BASH_REMATCH[1]}" + if validate_port "$port"; then + print_status "Valid listen port: $port" + else + print_error "Invalid listen port: $port" + ((errors++)) + fi + fi + fi + + # Parse Peer section + if [ $in_peer -eq 1 ]; then + if [[ $line =~ ^PublicKey[[:space:]]*=[[:space:]]*(.+)$ ]]; then + local public_key="${BASH_REMATCH[1]}" + if validate_wg_key "$public_key"; then + print_status "Valid peer public key" + else + print_error "Invalid peer public key format" + ((errors++)) + fi + elif [[ $line =~ ^AllowedIPs[[:space:]]*=[[:space:]]*(.+)$ ]]; then + local allowed_ips="${BASH_REMATCH[1]}" + # Split multiple IPs if present + IFS=',' read -ra IP_ARRAY <<< "$allowed_ips" + for ip in "${IP_ARRAY[@]}"; do + ip=$(echo "$ip" | xargs) # Trim whitespace + if validate_ip "$ip"; then + print_status "Valid allowed IP: $ip" + else + print_error "Invalid allowed IP format: $ip" + ((errors++)) + fi + done + elif [[ $line =~ ^Endpoint[[:space:]]*=[[:space:]]*(.+)$ ]]; then + local endpoint="${BASH_REMATCH[1]}" + if validate_endpoint "$endpoint"; then + print_status "Valid endpoint: $endpoint" + else + print_error "Invalid endpoint format: $endpoint" + ((errors++)) + fi + elif [[ $line =~ ^PersistentKeepalive[[:space:]]*=[[:space:]]*(.+)$ ]]; then + local keepalive="${BASH_REMATCH[1]}" + if validate_port "$keepalive"; then + print_status "Valid persistent keepalive: $keepalive" + else + print_error "Invalid persistent keepalive: $keepalive" + ((errors++)) + fi + fi + fi + done < "$config_file" + + # Check required fields + if [ $has_interface -eq 0 ]; then + print_error "Missing [Interface] section" + ((errors++)) + fi + + if [ $has_private_key -eq 0 ]; then + print_error "Missing PrivateKey in [Interface] section" + ((errors++)) + fi + + if [ $has_address -eq 0 ]; then + print_error "Missing Address in [Interface] section" + ((errors++)) + fi + + if [ $peer_count -eq 0 ]; then + print_warning "No [Peer] sections found" + ((warnings++)) + fi + + # Summary + echo "" + if [ $errors -eq 0 ] && [ $warnings -eq 0 ]; then + print_status "Configuration file is valid!" + return 0 + elif [ $errors -eq 0 ]; then + print_warning "Configuration file has $warnings warning(s)" + return 0 + else + print_error "Configuration file has $errors error(s) and $warnings warning(s)" + return 1 + fi +} + +# Function to validate all config files in a directory +validate_directory() { + local dir=$1 + local total_errors=0 + local total_files=0 + + print_header "Validating all config files in: $dir" + + if [ ! -d "$dir" ]; then + print_error "Directory not found: $dir" + return 1 + fi + + for config_file in "$dir"/*.conf; do + if [ -f "$config_file" ]; then + ((total_files++)) + if ! validate_config_file "$config_file"; then + ((total_errors++)) + fi + echo "" + fi + done + + if [ $total_files -eq 0 ]; then + print_warning "No .conf files found in $dir" + else + print_header "Validation Summary" + echo "Total files: $total_files" + echo "Files with errors: $total_errors" + echo "Files without errors: $((total_files - total_errors))" + fi +} + +# Show usage +show_usage() { + echo "Usage: $0 [OPTIONS] " + echo "" + echo "Options:" + echo " -h, --help Show this help message" + echo "" + echo "Examples:" + echo " $0 wireguard_configs/test.conf # Validate single file" + echo " $0 wireguard_configs/ # Validate all files in directory" + echo " $0 TESTING/ # Validate TESTING directory" +} + +# Main logic +if [ $# -eq 0 ] || [[ $1 =~ ^- ]]; then + show_usage + exit 0 +fi + +target=$1 + +if [ -f "$target" ]; then + validate_config_file "$target" +elif [ -d "$target" ]; then + validate_directory "$target" +else + print_error "Target not found: $target" + exit 1 +fi \ No newline at end of file diff --git a/wgtool b/wgtool new file mode 100755 index 0000000..9a1f699 Binary files /dev/null and b/wgtool differ diff --git a/wireguard_configs/cachybot_private.key b/wireguard_configs/cachybot_private.key new file mode 100644 index 0000000..74231dd --- /dev/null +++ b/wireguard_configs/cachybot_private.key @@ -0,0 +1 @@ +6Ag2qgqY85gMrp9gvbqjSAM6jJIGQds737HDfKnTM0s= \ No newline at end of file diff --git a/wireguard_configs/cachybot_public.key b/wireguard_configs/cachybot_public.key new file mode 100644 index 0000000..a974b7f --- /dev/null +++ b/wireguard_configs/cachybot_public.key @@ -0,0 +1 @@ +fqtkYEqAOBXQzmUGVNSloLawNyrVIOqV/Vv2FvVmrho= \ No newline at end of file diff --git a/wireguard_configs/cachybot_wg0.conf b/wireguard_configs/cachybot_wg0.conf new file mode 100644 index 0000000..0ec96d9 --- /dev/null +++ b/wireguard_configs/cachybot_wg0.conf @@ -0,0 +1,20 @@ +# ================================================ +# Node: cachybot +# PublicKey: fqtkYEqAOBXQzmUGVNSloLawNyrVIOqV/Vv2FvVmrho= +# +# Add this peer to Zion (/etc/wireguard/wg0.conf): +# [Peer] +# PublicKey = fqtkYEqAOBXQzmUGVNSloLawNyrVIOqV/Vv2FvVmrho= +# AllowedIPs = 10.8.0.26/32 +# ================================================ + +[Interface] +Address = 10.8.0.26/24 +PrivateKey = 6Ag2qgqY85gMrp9gvbqjSAM6jJIGQds737HDfKnTM0s= + +# Zion (central server) +[Peer] +PublicKey = 2ztJbrN1x1NWanzPGLiKL19ZkdOhm5Y7WeKEWBT5cyg= +AllowedIPs = 10.8.0.0/24 +Endpoint = ugh.im:51820 +PersistentKeepalive = 25 diff --git a/wireguard_configs/leviathan_private.key b/wireguard_configs/leviathan_private.key new file mode 100644 index 0000000..6eb9885 --- /dev/null +++ b/wireguard_configs/leviathan_private.key @@ -0,0 +1 @@ +CAco1G1O4Ffd6x8a5CDRpxMK35iO5JM/BkxKeC+Pu0c= \ No newline at end of file diff --git a/wireguard_configs/leviathan_public.key b/wireguard_configs/leviathan_public.key new file mode 100644 index 0000000..50308e6 --- /dev/null +++ b/wireguard_configs/leviathan_public.key @@ -0,0 +1 @@ +GEVjkJgHTvMRr9ka92dafoq1b8fPZ3HZyo2FgBsoqkE= \ No newline at end of file diff --git a/wireguard_configs/leviathan_wg0.conf b/wireguard_configs/leviathan_wg0.conf new file mode 100644 index 0000000..e9653f5 --- /dev/null +++ b/wireguard_configs/leviathan_wg0.conf @@ -0,0 +1,20 @@ +# ================================================ +# Node: leviathan +# PublicKey: GEVjkJgHTvMRr9ka92dafoq1b8fPZ3HZyo2FgBsoqkE= +# +# Add this peer to Zion (/etc/wireguard/wg0.conf): +# [Peer] +# PublicKey = GEVjkJgHTvMRr9ka92dafoq1b8fPZ3HZyo2FgBsoqkE= +# AllowedIPs = 10.8.0.24/32 +# ================================================ + +[Interface] +Address = 10.8.0.24/24 +PrivateKey = CAco1G1O4Ffd6x8a5CDRpxMK35iO5JM/BkxKeC+Pu0c= + +# Zion (central server) +[Peer] +PublicKey = 2ztJbrN1x1NWanzPGLiKL19ZkdOhm5Y7WeKEWBT5cyg= +AllowedIPs = 10.8.0.0/24 +Endpoint = ugh.im:51820 +PersistentKeepalive = 25 diff --git a/wireguard_configs/nano_private.key b/wireguard_configs/nano_private.key new file mode 100644 index 0000000..10aa5ad --- /dev/null +++ b/wireguard_configs/nano_private.key @@ -0,0 +1 @@ +CFh3Ksl6IXmnv+oku/o1vJDSnfC0F9LMZS9Zb/hWGk8= \ No newline at end of file diff --git a/wireguard_configs/nano_public.key b/wireguard_configs/nano_public.key new file mode 100644 index 0000000..7042d04 --- /dev/null +++ b/wireguard_configs/nano_public.key @@ -0,0 +1 @@ +FGrMAZPCUiQsFHW55PhkNr9AliR4+sw3uRLTbcilmFc= \ No newline at end of file diff --git a/wireguard_configs/nano_wg0.conf b/wireguard_configs/nano_wg0.conf new file mode 100644 index 0000000..0719810 --- /dev/null +++ b/wireguard_configs/nano_wg0.conf @@ -0,0 +1,20 @@ +# ================================================ +# Node: nano +# PublicKey: FGrMAZPCUiQsFHW55PhkNr9AliR4+sw3uRLTbcilmFc= +# +# Add this peer to Zion (/etc/wireguard/wg0.conf): +# [Peer] +# PublicKey = FGrMAZPCUiQsFHW55PhkNr9AliR4+sw3uRLTbcilmFc= +# AllowedIPs = 10.8.0.26/32 +# ================================================ + +[Interface] +Address = 10.8.0.26/24 +PrivateKey = CFh3Ksl6IXmnv+oku/o1vJDSnfC0F9LMZS9Zb/hWGk8= + +# Zion (central server) +[Peer] +PublicKey = 2ztJbrN1x1NWanzPGLiKL19ZkdOhm5Y7WeKEWBT5cyg= +AllowedIPs = 10.8.0.0/24 +Endpoint = ugh.im:51820 +PersistentKeepalive = 25 diff --git a/wireguard_configs/pix66.conf b/wireguard_configs/pix66.conf new file mode 100644 index 0000000..4b9cd72 --- /dev/null +++ b/wireguard_configs/pix66.conf @@ -0,0 +1,12 @@ +[Interface] +PrivateKey = iKNfR2fY/zI2mc8EDToXGrTR2mPOyMn1MY5/3Wq80HY= +Address = 10.8.0.6/32 + + + +#CTH +[Peer] +PublicKey = NBktXKy1s0n2lIlIMODvOqKNwAtYdoZH5feKt5P43i0= +AllowedIPs = 10.8.0.10/24 +Endpoint = aw2cd67.glddns.com:53535 +PersistentKeepalive = 25 diff --git a/wireguard_configs/pix66_summary.txt b/wireguard_configs/pix66_summary.txt new file mode 100644 index 0000000..094d624 --- /dev/null +++ b/wireguard_configs/pix66_summary.txt @@ -0,0 +1,22 @@ +WireGuard Configuration Summary for pix66 +=============================================== + +Node Name: pix66 +IP Address: 10.8.0.66/32 + + +Your Public Key: hguz42G5S8EV3NmkORc6eiBWb+V9Z6oBdiXVnAcqvmI= +Your Private Key: iKNfR2fY/zI2mc8EDToXGrTR2mPOyMn1MY5/3Wq80HY= + +Configuration File: wireguard_configs/pix66.conf + +Commands to use: +- Start: sudo wg-quick up pix66 +- Stop: sudo wg-quick down pix66 +- Status: sudo wg show + +Remember to: +1. Keep your private key secure +2. Share your public key with peers +3. Add peer public keys to your configuration +4. Set proper file permissions: chmod 600 wireguard_configs/pix66.conf diff --git a/wireguard_configs/pixel6_private.key b/wireguard_configs/pixel6_private.key new file mode 100644 index 0000000..c8a9878 --- /dev/null +++ b/wireguard_configs/pixel6_private.key @@ -0,0 +1 @@ +yK7AJfrfs6INevH2AZUWpCrltYqe81Pc+mZ/LJsInVc= \ No newline at end of file diff --git a/wireguard_configs/pixel6_public.key b/wireguard_configs/pixel6_public.key new file mode 100644 index 0000000..7ce9df8 --- /dev/null +++ b/wireguard_configs/pixel6_public.key @@ -0,0 +1 @@ +KHXrXOsLwYr8iDXXB/E0AVVG2EKFQKCGLY8aLniOUl4= \ No newline at end of file diff --git a/wireguard_configs/pixel6_wg_info.json b/wireguard_configs/pixel6_wg_info.json new file mode 100644 index 0000000..39e347d --- /dev/null +++ b/wireguard_configs/pixel6_wg_info.json @@ -0,0 +1,11 @@ +{ + "hostname": "pixel6", + "ip_address": "10.8.0.6", + "private_key": "yK7AJfrfs6INevH2AZUWpCrltYqe81Pc+mZ/LJsInVc=", + "public_key": "KHXrXOsLwYr8iDXXB/E0AVVG2EKFQKCGLY8aLniOUl4=", + "routing_mode": "full_tunnel", + "interface": "pixwg0", + "generated": "2025-08-10T05:59:41-07:00", + "script_version": "3.0.0", + "running_as_root": false +} \ No newline at end of file diff --git a/wireguard_setup.go b/wireguard_setup.go new file mode 100644 index 0000000..a73bdd6 --- /dev/null +++ b/wireguard_setup.go @@ -0,0 +1,859 @@ +package main + +import ( + "bufio" + "crypto/rand" + "encoding/base64" + "encoding/json" + "flag" + "fmt" + "net" + "os" + "os/exec" + "path/filepath" + "regexp" + "strconv" + "strings" + "time" + + "golang.org/x/crypto/curve25519" +) + +// Colors for terminal output +const ( + Red = "\033[0;31m" + Green = "\033[0;32m" + Yellow = "\033[1;33m" + Blue = "\033[0;34m" + Reset = "\033[0m" +) + +// Script constants +const ( + ScriptVersion = "3.0.0" + DefaultPort = "51820" + MaxHostname = 63 + MaxInterface = 15 +) + +// WireGuard peer configuration +type WireGuardPeer struct { + Name string + PublicKey string + AllowedIPs string + Endpoint string + PersistentKeepalive int + Description string +} + +// Node configuration +type NodeConfig struct { + Hostname string `json:"hostname"` + IPAddress string `json:"ip_address"` + PrivateKey string `json:"private_key"` + PublicKey string `json:"public_key"` + RoutingMode string `json:"routing_mode"` + Interface string `json:"interface"` + Generated string `json:"generated"` + ScriptVer string `json:"script_version"` + RunningRoot bool `json:"running_as_root"` +} + +// Application state +type AppState struct { + ForceMode bool + RunningAsRoot bool + WGDirectory string + StepCounter int +} + +// Default peers - automatically included in every config +var defaultPeers = []WireGuardPeer{ + { + Name: "Zion", + PublicKey: "2ztJbrN1x1NWanzPGLiKL19ZkdOhm5Y7WeKEWBT5cyg=", + AllowedIPs: "10.8.0.0/24", + Endpoint: "ugh.im:51820", + PersistentKeepalive: 25, + Description: "Central server (always included)", + }, + { + Name: "CTH", + PublicKey: "NBktXKy1s0n2lIlIMODvOqKNwAtYdoZH5feKt5P43i0=", + AllowedIPs: "10.8.0.10/32", + Endpoint: "aw2cd67.glddns.com:53535", + PersistentKeepalive: 25, + Description: "Secondary server (always included)", + }, +} + +// Reserved IP addresses +var reservedIPs = map[string]string{ + "10.8.0.1": "Zion", + "10.8.0.10": "CTH", + "10.8.0.2": "Aza", + "10.8.0.20": "Nyar", + "10.8.0.99": "Galaxy", + "10.8.0.7": "nanocube", + "10.8.0.42": "jupiter", + "10.8.0.8": "HASS", + "10.8.0.40": "framebot", +} + +// Static server ports +var staticServerPorts = map[string]string{ + "10.8.0.1": "51820", // Zion + "10.8.0.10": "53535", // CTH + "10.8.0.99": "54382", // Galaxy +} + +// Validation regexes +var ( + ipRegex = regexp.MustCompile(`^10\.8\.0\.\d{1,3}$`) + hostnameRegex = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]$|^[a-zA-Z0-9]$`) + interfaceRegex = regexp.MustCompile(`^[a-zA-Z][a-zA-Z0-9]*$`) + keyRegex = regexp.MustCompile(`^[A-Za-z0-9+/]{43}=$`) +) + +// Utility functions +func step(n int, msg string) { + fmt.Printf("%sStep %d:%s %s\n", Blue, n, Reset, msg) +} + +func printStatus(message string) { + fmt.Printf("%s[INFO]%s %s\n", Green, Reset, message) +} + +func printWarning(message string) { + fmt.Printf("%s[WARNING]%s %s\n", Yellow, Reset, message) +} + +func printError(message string) { + fmt.Printf("%s[ERROR]%s %s\n", Red, Reset, message) +} + +func printHeader(title string) { + fmt.Printf("%s================================%s\n", Blue, Reset) + fmt.Printf("%s%s%s\n", Blue, title, Reset) + fmt.Printf("%s================================%s\n", Blue, Reset) + fmt.Println() +} + +// Validation functions +func validateHostname(hostname string) error { + if hostname == "" { + return fmt.Errorf("hostname cannot be empty") + } + if len(hostname) > MaxHostname { + return fmt.Errorf("hostname too long (max %d characters)", MaxHostname) + } + if !hostnameRegex.MatchString(hostname) { + return fmt.Errorf("invalid hostname format. Use alphanumeric characters and hyphens only") + } + return nil +} + +func validateIP(ip string) error { + if ip == "" { + return fmt.Errorf("IP address cannot be empty") + } + + if !ipRegex.MatchString(ip) { + return fmt.Errorf("IP should be in 10.8.0.x range for NextGen network") + } + + if parsedIP := net.ParseIP(ip); parsedIP == nil { + return fmt.Errorf("invalid IP address format") + } + + if peerName, exists := reservedIPs[ip]; exists { + return fmt.Errorf("IP %s is already reserved for %s", ip, peerName) + } + + return nil +} + +func validateInterface(name string) error { + if name == "" { + return nil // Allow empty for default + } + if len(name) > MaxInterface { + return fmt.Errorf("interface name too long (max %d characters)", MaxInterface) + } + if !interfaceRegex.MatchString(name) { + return fmt.Errorf("interface name must start with a letter and contain only letters and numbers") + } + return nil +} + +func validateWireGuardKey(key string) error { + if key == "" { + return fmt.Errorf("key cannot be empty") + } + if !keyRegex.MatchString(key) { + return fmt.Errorf("invalid WireGuard key format") + } + return nil +} + +// User input functions +func getUserInput(prompt string, validator func(string) error, forceMode bool) string { + reader := bufio.NewReader(os.Stdin) + for { + fmt.Print(prompt) + input, _ := reader.ReadString('\n') + input = strings.TrimSpace(input) + + if err := validator(input); err != nil { + if forceMode { + printWarning(fmt.Sprintf("Validation failed: %s, but continuing due to force mode", err.Error())) + return input + } + printError(err.Error()) + fmt.Print("Continue anyway? (y/N): ") + response, _ := reader.ReadString('\n') + response = strings.TrimSpace(strings.ToLower(response)) + if response == "y" || response == "yes" { + return input + } + continue + } + return input + } +} + +func getDirectoryInput(prompt, defaultDir string, forceMode bool) string { + reader := bufio.NewReader(os.Stdin) + for { + fmt.Printf("%s [%s]: ", prompt, defaultDir) + input, _ := reader.ReadString('\n') + input = strings.TrimSpace(input) + + if input == "" { + input = defaultDir + } + + // Check if directory exists + if _, err := os.Stat(input); os.IsNotExist(err) { + if forceMode { + if err := os.MkdirAll(input, 0755); err != nil { + printError(fmt.Sprintf("Failed to create directory '%s': %v", input, err)) + continue + } + } else { + fmt.Printf("Directory '%s' doesn't exist. Create it? (Y/n): ", input) + response, _ := reader.ReadString('\n') + response = strings.TrimSpace(strings.ToLower(response)) + if response == "n" || response == "no" { + continue + } + + if err := os.MkdirAll(input, 0755); err != nil { + printError(fmt.Sprintf("Failed to create directory '%s': %v", input, err)) + continue + } + } + } + + // Check if directory is writable + if info, err := os.Stat(input); err == nil { + if info.Mode()&0200 == 0 { + printError(fmt.Sprintf("Directory '%s' is not writable", input)) + continue + } + } + + return input + } +} + +func getYesNoInput(prompt string, defaultYes bool, forceMode bool) bool { + if forceMode { + return defaultYes + } + + reader := bufio.NewReader(os.Stdin) + for { + if defaultYes { + fmt.Printf("%s (Y/n): ", prompt) + } else { + fmt.Printf("%s (y/N): ", prompt) + } + input, _ := reader.ReadString('\n') + input = strings.TrimSpace(strings.ToLower(input)) + + if input == "" { + return defaultYes + } + if input == "y" || input == "yes" { + return true + } + if input == "n" || input == "no" { + return false + } + printError("Please enter 'y' or 'n'") + } +} + +func getChoice(prompt string, choices []string, forceMode bool) int { + if forceMode && len(choices) > 0 { + return 1 // Default to first choice in force mode + } + + reader := bufio.NewReader(os.Stdin) + for { + fmt.Println(prompt) + for i, choice := range choices { + fmt.Printf("%d. %s\n", i+1, choice) + } + fmt.Print("Enter your choice: ") + input, _ := reader.ReadString('\n') + input = strings.TrimSpace(input) + + if choice, err := strconv.Atoi(input); err == nil && choice >= 1 && choice <= len(choices) { + return choice + } + printError(fmt.Sprintf("Invalid choice. Please enter a number between 1 and %d.", len(choices))) + } +} + +// WireGuard key generation (safe version) +func generateWireGuardKeys() (string, string, error) { + // Generate private key + privateKeyBytes := make([]byte, 32) + if _, err := rand.Read(privateKeyBytes); err != nil { + return "", "", fmt.Errorf("failed to generate random bytes: %w", err) + } + + // Ensure the key is valid for curve25519 + privateKeyBytes[0] &= 248 + privateKeyBytes[31] &= 127 + privateKeyBytes[31] |= 64 + + // Generate public key (safe conversion) + var privateKeyArray [32]byte + copy(privateKeyArray[:], privateKeyBytes) + + var publicKeyBytes [32]byte + curve25519.ScalarBaseMult(&publicKeyBytes, &privateKeyArray) + + privateKey := base64.StdEncoding.EncodeToString(privateKeyBytes[:]) + publicKey := base64.StdEncoding.EncodeToString(publicKeyBytes[:]) + + return privateKey, publicKey, nil +} + +// Configuration generation +func generateConfig(hostname, ipAddress, privateKey, routingMode, interfaceName string) string { + var config strings.Builder + + // Interface section + config.WriteString("[Interface]\n") + config.WriteString(fmt.Sprintf("Address = %s/24\n", ipAddress)) + config.WriteString(fmt.Sprintf("PrivateKey = %s\n", privateKey)) + + // Add ListenPort for static servers + if port, exists := staticServerPorts[ipAddress]; exists { + config.WriteString(fmt.Sprintf("ListenPort = %s\n", port)) + } + + // Add DNS for full tunnel mode + if routingMode == "full_tunnel" { + config.WriteString("DNS = 1.1.1.1, 8.8.8.8\n") + } + + // Add default peers + for _, peer := range defaultPeers { + config.WriteString(fmt.Sprintf("\n# %s (%s)\n", peer.Name, peer.Description)) + config.WriteString("[Peer]\n") + config.WriteString(fmt.Sprintf("PublicKey = %s\n", peer.PublicKey)) + + // Set AllowedIPs based on routing mode + if routingMode == "full_tunnel" { + config.WriteString("AllowedIPs = 0.0.0.0/0, ::/0\n") + } else { + config.WriteString(fmt.Sprintf("AllowedIPs = %s\n", peer.AllowedIPs)) + } + + config.WriteString(fmt.Sprintf("Endpoint = %s\n", peer.Endpoint)) + config.WriteString(fmt.Sprintf("PersistentKeepalive = %d\n", peer.PersistentKeepalive)) + } + + return config.String() +} + +// Generate peer configuration for Zion +func generateZionPeerConfig(hostname, publicKey, ipAddress string) string { + var config strings.Builder + config.WriteString(fmt.Sprintf("# %s\n", hostname)) + config.WriteString("[Peer]\n") + config.WriteString(fmt.Sprintf("PublicKey = %s\n", publicKey)) + config.WriteString(fmt.Sprintf("AllowedIPs = %s/32\n", ipAddress)) + return config.String() +} + +// System checks +func checkDependencies() error { + deps := []string{"wg", "wg-quick"} + + for _, dep := range deps { + if _, err := exec.LookPath(dep); err != nil { + return fmt.Errorf("missing dependency: %s", dep) + } + } + return nil +} + +func isRunningAsRoot() bool { + return os.Geteuid() == 0 +} + +func checkFileExists(filepath string, forceMode bool) bool { + if _, err := os.Stat(filepath); err == nil { + if forceMode { + printWarning(fmt.Sprintf("File '%s' already exists, overwriting due to force mode", filepath)) + return true + } + printWarning(fmt.Sprintf("File '%s' already exists", filepath)) + return getYesNoInput("Do you want to overwrite it?", false, false) + } + return true +} + +// Display instructions +func displayZionInstructions(hostname, publicKey, ipAddress string) { + printHeader("IMPORTANT: Update Zion Server Configuration") + fmt.Println() + printWarning("You MUST add this peer to Zion's configuration file:") + fmt.Println(" /etc/wireguard/wg0.conf") + fmt.Println() + + fmt.Println("Add the following peer section to Zion's config:") + fmt.Println("----------------------------------------") + fmt.Println(generateZionPeerConfig(hostname, publicKey, ipAddress)) + fmt.Println("----------------------------------------") + fmt.Println() + + printWarning("After updating Zion's config:") + fmt.Println("1. Save the file") + fmt.Println("2. Restart Zion's WireGuard: sudo systemctl restart wg-quick@wg0") + fmt.Println("3. Then start this node's WireGuard: sudo wg-quick up " + hostname) + fmt.Println() + + // Show Zion's current configuration structure + fmt.Println("Zion's current configuration structure:") + fmt.Println("----------------------------------------") + fmt.Println("[Interface]") + fmt.Println("Address = 10.8.0.1/24") + fmt.Println("ListenPort = 51820") + fmt.Println("PrivateKey = ") + fmt.Println("PostUp = ") + fmt.Println("PostDown = ") + fmt.Println() + + // Show existing peers + for name, pubKey := range map[string]string{ + "Cth": "NBktXKy1s0n2lIlIMODvOqKNwAtYdoZH5feKt5P43i0=", + "Aza": "qmTKA257DLOrfhk5Zw8RyRmBSonmm6epbloT0P0ZWDc=", + "Nyar": "2BA7L1oJP1tK6dIUNHMgcZmOmYmlyPRe2RaBqfUsEWo=", + "Galaxy": "QBNt00VSedxPlq3ZvsdYaqIcbudCAyxv9TG65aPVZzM=", + "nanocube": "/ZImoATDIS0e0N08CD7mqWbhtGlSnynpPuY04Ed4Zyc=", + "jupiter": "YIFQ43ULk/YoCgOv3SBU6+MOrbxd+mlvaw9rT8uoNmw=", + "HASS": "C+Poz/7DaXCxe4HZiL6D5cld4jMt5o1gBq3iPiBzrg0=", + "framebot": "loS3yZapqmt6lP53Q+s4EvUzw6FmwgZC8jzgLluJ1Es=", + } { + fmt.Printf("#%s\n", name) + fmt.Println("[Peer]") + fmt.Printf("PublicKey = %s\n", pubKey) + fmt.Printf("AllowedIPs = 10.8.0.x/32\n") + fmt.Println() + } + + fmt.Println("# Add your peer here:") + fmt.Printf("# %s\n", hostname) + fmt.Println("# [Peer]") + fmt.Printf("# PublicKey = %s\n", publicKey) + fmt.Printf("# AllowedIPs = %s/32\n", ipAddress) + fmt.Println("----------------------------------------") + fmt.Println() +} + +func displayFullTunnelInstructions() { + fmt.Println() + printWarning("FULL TUNNEL MODE DETECTED - Endpoint Changes Required:") + fmt.Println() + fmt.Println("Since this node will route ALL traffic through the VPN, you need to:") + fmt.Println() + fmt.Println("1. Update Zion's config (/etc/wireguard/wg0.conf) to allow this peer:") + fmt.Println(" - Add the peer section as shown above") + fmt.Println(" - Ensure Zion has proper iptables rules for NAT/masquerading") + fmt.Println() + fmt.Println("2. Check Zion's iptables rules (run on Zion server):") + fmt.Println(" sudo iptables -t nat -L POSTROUTING") + fmt.Println(" sudo iptables -L FORWARD") + fmt.Println() + fmt.Println("3. If Zion doesn't have proper NAT rules, add them:") + fmt.Println(" sudo iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE") + fmt.Println(" sudo iptables -A FORWARD -i wg0 -j ACCEPT") + fmt.Println(" sudo iptables -A FORWARD -o wg0 -j ACCEPT") + fmt.Println() + fmt.Println("4. Enable IP forwarding on Zion (if not already enabled):") + fmt.Println(" echo 'net.ipv4.ip_forward=1' | sudo tee -a /etc/sysctl.conf") + fmt.Println(" sudo sysctl -p") + fmt.Println() +} + +func displayNextSteps(hostname, interfaceName, publicKey, ipAddress, routingMode string, runningAsRoot bool) { + step(4, "Next Steps") + fmt.Println() + + if runningAsRoot { + printStatus("Ready to start WireGuard:") + fmt.Printf(" systemctl enable --now wg-quick@%s\n", interfaceName) + } else { + printWarning("To enable WireGuard (requires root):") + fmt.Printf(" sudo cp %s /etc/wireguard/\n", filepath.Join("wireguard_configs", fmt.Sprintf("%s.conf", interfaceName))) + fmt.Printf(" sudo chmod 600 /etc/wireguard/%s.conf\n", interfaceName) + fmt.Printf(" sudo systemctl enable --now wg-quick@%s\n", interfaceName) + } + fmt.Println() + printWarning("IMPORTANT: Update other nodes with this peer info:") + fmt.Printf(" PublicKey = %s\n", publicKey) + fmt.Printf(" AllowedIPs = %s/32\n", ipAddress) + fmt.Println() + + if routingMode == "full_tunnel" { + displayFullTunnelInstructions() + } + + // Zion update instructions + fmt.Printf("%s========================================%s\n", Red, Reset) + fmt.Printf("%s !!! NOW UPDATE ZION SERVER !!! %s\n", Red, Reset) + fmt.Printf("%s========================================%s\n", Red, Reset) + fmt.Println() + printWarning("You MUST add this peer to Zion's config (/etc/wireguard/wg0.conf):") + fmt.Println() + fmt.Printf("%s#%s%s\n", Yellow, hostname, Reset) + fmt.Printf("%s[Peer]%s\n", Yellow, Reset) + fmt.Printf("%sPublicKey = %s%s\n", Yellow, publicKey, Reset) + fmt.Printf("%sAllowedIPs = %s/32%s\n", Yellow, ipAddress, Reset) + fmt.Println() + printWarning("After updating Zion, restart its WireGuard:") + fmt.Println(" systemctl restart wg-quick@wg0") + fmt.Println() + + if runningAsRoot { + printWarning("Then restart this node's WireGuard:") + fmt.Printf(" systemctl restart wg-quick@%s\n", interfaceName) + } else { + printWarning("Then restart this node's WireGuard:") + fmt.Printf(" sudo systemctl restart wg-quick@%s\n", interfaceName) + } +} + +// Main function +func main() { + // Parse command line flags + var forceMode bool + flag.BoolVar(&forceMode, "force", false, "Skip confirmations and use defaults") + flag.Parse() + + // Initialize application state + state := &AppState{ + ForceMode: forceMode, + RunningAsRoot: isRunningAsRoot(), + StepCounter: 1, + } + + printHeader("WireGuard Configuration Setup v" + ScriptVersion) + fmt.Println("This script will help you create WireGuard keys and configuration files.") + fmt.Println("Based on the CURRENT_WORKING configuration with Zion as central server.") + fmt.Println("Default peers (Zion & CTH) will be automatically included.") + fmt.Println() + + if forceMode { + printWarning("Force mode enabled - skipping confirmations and using defaults") + fmt.Println() + } + + // Determine WireGuard directory + if state.RunningAsRoot { + state.WGDirectory = "/etc/wireguard" + printStatus("Running as root - using system directories") + printStatus(fmt.Sprintf("WireGuard directory: %s", state.WGDirectory)) + } else { + state.WGDirectory = "wireguard_configs" + printWarning("Not running as root - using current directory") + printStatus(fmt.Sprintf("WireGuard directory: %s", state.WGDirectory)) + printWarning("You'll need to manually copy config files to /etc/wireguard/ later") + } + fmt.Println() + + // Check dependencies + if err := checkDependencies(); err != nil { + printError(err.Error()) + printStatus("Install with: apt install wireguard-tools") + os.Exit(1) + } + + // Get directory for non-root users + if !state.RunningAsRoot { + step(state.StepCounter, "Directory Selection") + state.StepCounter++ + printStatus("Choose where to save WireGuard files:") + fmt.Printf(" - Current directory: %s\n", state.WGDirectory) + fmt.Printf(" - Home directory: %s\n", os.Getenv("HOME")) + fmt.Println(" - Custom directory") + fmt.Println() + state.WGDirectory = getDirectoryInput("Enter directory path for WireGuard files", state.WGDirectory, forceMode) + fmt.Println() + } + + // Create directory + if err := os.MkdirAll(state.WGDirectory, 0755); err != nil { + printError(fmt.Sprintf("Failed to create directory: %v", err)) + os.Exit(1) + } + + // Get node information + step(state.StepCounter, "Node Information") + state.StepCounter++ + fmt.Println() + + hostname := getUserInput("Enter hostname for this node: ", validateHostname, forceMode) + + // Get IP address + fmt.Println() + fmt.Println("Available IP ranges:") + fmt.Println(" - 10.8.0.x (recommended for NextGen network)") + fmt.Println(" - 10.0.0.x (alternative range)") + fmt.Println() + + ipAddress := getUserInput("Enter IP address for this node (e.g., 10.8.0.30): ", validateIP, forceMode) + + // Get interface name + fmt.Println() + fmt.Println("Interface name options:") + fmt.Println(" - wg0 (default, most common)") + fmt.Println(" - wg1, wg2, etc. (if wg0 is already in use)") + fmt.Println(" - Custom name (e.g., nextgen, vpn, etc.)") + fmt.Println() + + interfaceName := getUserInput("Enter interface name (default: wg0): ", validateInterface, forceMode) + if interfaceName == "" { + interfaceName = "wg0" + } + + // Check if configuration file already exists + configFile := filepath.Join(state.WGDirectory, fmt.Sprintf("%s.conf", interfaceName)) + if !checkFileExists(configFile, forceMode) { + printError("Operation cancelled. Please choose a different interface name or remove the existing file.") + os.Exit(1) + } + + // Configuration options + fmt.Println() + step(state.StepCounter, "Configuration Options") + state.StepCounter++ + fmt.Println() + fmt.Println("Choose an option:") + fmt.Println("1. Generate keys only (manual config creation)") + fmt.Println("2. Generate keys + complete config (recommended)") + fmt.Println() + + var configChoice int + if forceMode { + configChoice = 2 // Default to complete config in force mode + printStatus("Force mode: Using complete config generation") + } else { + configChoice = getChoice("Enter your choice:", []string{"Generate keys only", "Generate keys + complete config"}, false) + } + + // Traffic routing options + var routingMode string + if configChoice == 2 { + fmt.Println() + fmt.Println("Traffic routing options:") + fmt.Println("1. WireGuard traffic only (10.8.0.x network only)") + fmt.Println("2. All traffic through VPN (full tunnel)") + fmt.Println() + fmt.Println("Note: Full tunnel routes ALL internet traffic through the VPN.") + fmt.Println(" WireGuard-only keeps your regular internet traffic separate.") + fmt.Println() + + var routingChoice int + if forceMode { + routingChoice = 1 // Default to WireGuard-only in force mode + printStatus("Force mode: Using WireGuard-only routing") + } else { + routingChoice = getChoice("Enter your choice:", []string{"WireGuard traffic only", "All traffic through VPN"}, false) + } + + if routingChoice == 1 { + routingMode = "wg_only" + printStatus("Selected: WireGuard traffic only") + } else { + routingMode = "full_tunnel" + printStatus("Selected: All traffic through VPN") + } + } + + printStatus(fmt.Sprintf("Starting setup for %s (%s)...", hostname, ipAddress)) + fmt.Println() + + // Generate keys + printStatus("Generating WireGuard keys...") + privateKey, publicKey, err := generateWireGuardKeys() + if err != nil { + printError(fmt.Sprintf("Failed to generate keys: %v", err)) + os.Exit(1) + } + + // Save keys + privateKeyFile := filepath.Join(state.WGDirectory, fmt.Sprintf("%s_private.key", hostname)) + publicKeyFile := filepath.Join(state.WGDirectory, fmt.Sprintf("%s_public.key", hostname)) + + if err := os.WriteFile(privateKeyFile, []byte(privateKey), 0600); err != nil { + printError(fmt.Sprintf("Failed to save private key: %v", err)) + os.Exit(1) + } + + if err := os.WriteFile(publicKeyFile, []byte(publicKey), 0600); err != nil { + printError(fmt.Sprintf("Failed to save public key: %v", err)) + os.Exit(1) + } + + printStatus("Keys generated successfully!") + fmt.Printf(" Private key: %s\n", privateKeyFile) + fmt.Printf(" Public key: %s\n", publicKeyFile) + fmt.Println() + + // Display node information + step(state.StepCounter, "Node Information") + state.StepCounter++ + fmt.Println("==========================================") + fmt.Printf("HOSTNAME: %s\n", hostname) + fmt.Printf("IP ADDRESS: %s\n", ipAddress) + fmt.Printf("PRIVATE KEY: %s\n", privateKey) + fmt.Printf("PUBLIC KEY: %s\n", publicKey) + fmt.Printf("INTERFACE: %s\n", interfaceName) + fmt.Printf("ROUTING MODE: %s\n", routingMode) + fmt.Println("==========================================") + fmt.Println() + + // Save structured info + infoFile := filepath.Join(state.WGDirectory, fmt.Sprintf("%s_wg_info.json", hostname)) + if state.RunningAsRoot { + infoFile = filepath.Join("/tmp", fmt.Sprintf("%s_wg_info.json", hostname)) + } + + nodeConfig := NodeConfig{ + Hostname: hostname, + IPAddress: ipAddress, + PrivateKey: privateKey, + PublicKey: publicKey, + RoutingMode: routingMode, + Interface: interfaceName, + Generated: time.Now().Format(time.RFC3339), + ScriptVer: ScriptVersion, + RunningRoot: state.RunningAsRoot, + } + + infoData, err := json.MarshalIndent(nodeConfig, "", " ") + if err != nil { + printError(fmt.Sprintf("Failed to marshal config: %v", err)) + os.Exit(1) + } + + if err := os.WriteFile(infoFile, infoData, 0600); err != nil { + printError(fmt.Sprintf("Failed to save info file: %v", err)) + os.Exit(1) + } + + printStatus(fmt.Sprintf("Information saved to: %s", infoFile)) + fmt.Println() + + // Generate complete config if requested + if configChoice == 2 { + printStatus(fmt.Sprintf("Generating complete %s.conf...", interfaceName)) + configContent := generateConfig(hostname, ipAddress, privateKey, routingMode, interfaceName) + + if err := os.WriteFile(configFile, []byte(configContent), 0600); err != nil { + printError(fmt.Sprintf("Failed to write config file: %v", err)) + os.Exit(1) + } + + printStatus(fmt.Sprintf("Config written to: %s", configFile)) + if state.RunningAsRoot { + printStatus("Permissions set to 600") + } + fmt.Println() + + // Display Zion integration instructions + displayZionInstructions(hostname, publicKey, ipAddress) + + // Display next steps + displayNextSteps(hostname, interfaceName, publicKey, ipAddress, routingMode, state.RunningAsRoot) + + // Config preview + fmt.Printf("%sConfig Preview:%s\n", Blue, Reset) + fmt.Println("----------------------------------------") + lines := strings.Split(configContent, "\n") + for i, line := range lines { + if i >= 8 { // Show more lines to include default peers + break + } + fmt.Println(line) + } + fmt.Printf(" [... and %d total lines]\n", len(lines)) + fmt.Println("----------------------------------------") + fmt.Println() + } else { + // Manual config generation path + step(state.StepCounter, "Next Steps") + state.StepCounter++ + fmt.Println() + printStatus("Keys generated successfully!") + fmt.Printf(" Private key: %s\n", privateKeyFile) + fmt.Printf(" Public key: %s\n", publicKeyFile) + fmt.Println() + printWarning("Next steps:") + fmt.Printf(" - Create %s.conf manually using the keys above\n", interfaceName) + fmt.Printf(" - Copy config to %s\n", filepath.Join(state.WGDirectory, fmt.Sprintf("%s.conf", interfaceName))) + if state.RunningAsRoot { + fmt.Printf(" - Set permissions: chmod 600 %s\n", filepath.Join(state.WGDirectory, fmt.Sprintf("%s.conf", interfaceName))) + fmt.Printf(" - Enable/start: systemctl enable --now wg-quick@%s\n", interfaceName) + } else { + fmt.Printf(" - Copy to system: sudo cp %s /etc/wireguard/\n", filepath.Join(state.WGDirectory, fmt.Sprintf("%s.conf", interfaceName))) + fmt.Printf(" - Set permissions: sudo chmod 600 /etc/wireguard/%s.conf\n", interfaceName) + fmt.Printf(" - Enable/start: sudo systemctl enable --now wg-quick@%s\n", interfaceName) + } + fmt.Println() + fmt.Printf("%s========================================%s\n", Red, Reset) + fmt.Printf("%s !!! NOW UPDATE ZION SERVER !!! %s\n", Red, Reset) + fmt.Printf("%s========================================%s\n", Red, Reset) + fmt.Println() + printWarning("You MUST add this peer to Zion's config (/etc/wireguard/wg0.conf):") + fmt.Println() + fmt.Printf("%s#%s%s\n", Yellow, hostname, Reset) + fmt.Printf("%s[Peer]%s\n", Yellow, Reset) + fmt.Printf("%sPublicKey = %s%s\n", Yellow, publicKey, Reset) + fmt.Printf("%sAllowedIPs = %s/32%s\n", Yellow, ipAddress, Reset) + fmt.Println() + printWarning("After updating Zion, restart its WireGuard:") + fmt.Println(" systemctl restart wg-quick@wg0") + fmt.Println() + + if state.RunningAsRoot { + printWarning("Then restart this node's WireGuard:") + fmt.Printf(" systemctl restart wg-quick@%s\n", interfaceName) + } else { + printWarning("Then restart this node's WireGuard:") + fmt.Printf(" sudo systemctl restart wg-quick@%s\n", interfaceName) + } + } + + fmt.Println() + printStatus(fmt.Sprintf("Setup complete for %s!", hostname)) + fmt.Println() +} diff --git a/wireguard_setup.sh b/wireguard_setup.sh new file mode 100755 index 0000000..1d69f39 --- /dev/null +++ b/wireguard_setup.sh @@ -0,0 +1,518 @@ +#!/usr/bin/env bash + +# WireGuard Setup Script +# This script guides users through creating WireGuard keys and configuration files + +set -euo pipefail # Exit on error, undefined vars, pipe failures + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Configuration file for Zion settings +ZION_CONFIG_FILE="$(dirname "$0")/CURRENT_WORKING/zion.conf" + +# Function to print colored output +print_status() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +print_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +print_header() { + echo -e "${BLUE}================================${NC}" + echo -e "${BLUE}$1${NC}" + echo -e "${BLUE}================================${NC}" +} + +# Function to check if command exists +command_exists() { + command -v "$1" >/dev/null 2>&1 +} + +# Function to validate IP address with subnet +validate_ip() { + local ip=$1 + + # Check if input contains subnet + if [[ ! $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/[0-9]{1,2}$ ]]; then + return 1 + fi + + # Extract IP and subnet parts + local ip_part="${ip%/*}" + local subnet="${ip#*/}" + + # Validate subnet + if [[ ! $subnet =~ ^[0-9]+$ ]] || [ "$subnet" -lt 1 ] || [ "$subnet" -gt 32 ]; then + return 1 + fi + + # Validate each octet + IFS='.' read -ra OCTETS <<< "$ip_part" + if [ ${#OCTETS[@]} -ne 4 ]; then + return 1 + fi + + for octet in "${OCTETS[@]}"; do + if [[ ! $octet =~ ^[0-9]+$ ]] || [ "$octet" -lt 0 ] || [ "$octet" -gt 255 ]; then + return 1 + fi + done + + return 0 +} + +# Function to validate port number +validate_port() { + local port=$1 + + if [[ ! $port =~ ^[0-9]+$ ]]; then + return 1 + fi + + if [ "$port" -lt 1 ] || [ "$port" -gt 65535 ]; then + return 1 + fi + + # Check for common reserved ports + if [ "$port" -le 1024 ]; then + print_warning "Port $port is in privileged range (1-1024). You may need root access." + fi + + return 0 +} + +# Function to validate WireGuard public key +validate_public_key() { + local key=$1 + + # WireGuard keys are base64 encoded and exactly 44 characters long + if [[ ! $key =~ ^[A-Za-z0-9+/]{43}=$ ]]; then + return 1 + fi + + return 0 +} + +# Function to detect network interface +detect_network_interface() { + # Try to detect the main network interface + local interface + + # Check for common interface names + for name in eth0 ens33 ens160 enp0s3 eno1; do + if ip link show "$name" >/dev/null 2>&1; then + interface="$name" + break + fi + done + + # Fallback to first non-loopback interface + if [ -z "$interface" ]; then + interface=$(ip route | grep default | awk '{print $5}' | head -1) + fi + + echo "$interface" +} + +# Function to load Zion configuration +load_zion_config() { + if [[ -f "$ZION_CONFIG_FILE" ]]; then + print_status "Found Zion configuration file: $ZION_CONFIG_FILE" + return 0 + else + print_warning "Zion configuration file not found: $ZION_CONFIG_FILE" + print_warning "Using hardcoded Zion configuration" + return 1 + fi +} + +# Function to create safe filename +safe_filename() { + local name="$1" + # Replace invalid characters with underscores + echo "$name" | sed 's/[^a-zA-Z0-9._-]/_/g' +} + +# Main setup function +main() { + print_header "WireGuard Configuration Setup" + echo "This script will help you create WireGuard keys and configuration files." + echo "Based on the CURRENT_WORKING configuration with Zion as central server." + echo "" + + # Check if wg command exists + if ! command_exists wg; then + print_error "WireGuard tools not found. Please install WireGuard first:" + echo " Ubuntu/Debian: sudo apt install wireguard" + echo " CentOS/RHEL: sudo yum install wireguard-tools" + echo " Arch: sudo pacman -S wireguard-tools" + echo " Fedora: sudo dnf install wireguard-tools" + exit 1 + fi + + # Create output directory + OUTPUT_DIR="wireguard_configs" + mkdir -p "$OUTPUT_DIR" + print_status "Created output directory: $OUTPUT_DIR" + + # Step 1: Generate keys + print_header "Step 1: Generate WireGuard Keys" + + echo "Generating private and public keys..." + PRIVATE_KEY=$(wg genkey) + PUBLIC_KEY=$(echo "$PRIVATE_KEY" | wg pubkey) + + print_status "Keys generated successfully!" + echo "Private Key: $PRIVATE_KEY" + echo "Public Key: $PUBLIC_KEY" + echo "" + + # Step 2: Get configuration details + print_header "Step 2: Configuration Details" + + # Get node name + local node_name + while true; do + read -p "Enter node name (e.g., aza, cth, galaxy): " node_name + node_name=$(safe_filename "$node_name") + + if [ -n "$node_name" ]; then + break + else + print_error "Node name cannot be empty" + fi + done + + # Check if configuration file already exists + CONFIG_FILE="$OUTPUT_DIR/${node_name}.conf" + if [ -f "$CONFIG_FILE" ]; then + print_warning "Configuration file '$CONFIG_FILE' already exists." + read -p "Do you want to overwrite it? (y/n): " OVERWRITE_FILE + if [[ ! $OVERWRITE_FILE =~ ^[Yy]$ ]]; then + print_error "Operation cancelled. Please choose a different node name or remove the existing file." + exit 1 + fi + print_status "Will overwrite existing configuration file." + fi + + # Get IP address + local ip_address + while true; do + read -p "Enter IP address with subnet (e.g., 10.8.0.2/24): " ip_address + if validate_ip "$ip_address"; then + break + else + print_error "Invalid IP address format. Use format: x.x.x.x/y" + fi + done + + # Ask if this is a server (listening) or client + read -p "Is this a server that will listen for connections? (y/n): " IS_SERVER + local listen_port + if [[ $IS_SERVER =~ ^[Yy]$ ]]; then + while true; do + read -p "Enter listen port (1-65535): " listen_port + if validate_port "$listen_port"; then + break + else + print_error "Invalid port number. Must be between 1-65535" + fi + done + fi + + # Step 3: Create configuration file + print_header "Step 3: Create Configuration File" + + # Create the configuration file + cat > "$CONFIG_FILE" << EOF +[Interface] +PrivateKey = $PRIVATE_KEY +Address = $ip_address +EOF + + # Add listen port if it's a server + if [[ $IS_SERVER =~ ^[Yy]$ ]]; then + echo "ListenPort = $listen_port" >> "$CONFIG_FILE" + fi + + # Add PostUp/PostDown rules for server (Zion-like configuration) + if [[ $IS_SERVER =~ ^[Yy]$ ]]; then + local network_interface + network_interface=$(detect_network_interface) + + if [ -n "$network_interface" ]; then + print_status "Detected network interface: $network_interface" + else + print_warning "Could not detect network interface, using 'eth0'" + network_interface="eth0" + fi + + echo "" >> "$CONFIG_FILE" + echo "# Server configuration - enable IP forwarding" >> "$CONFIG_FILE" + echo "PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o $network_interface -j MASQUERADE; ip route add 10.8.0.0/24 dev wg0 2>/dev/null || true" >> "$CONFIG_FILE" + echo "PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o $network_interface -j MASQUERADE; ip route del 10.8.0.0/24 dev wg0 2>/dev/null || true" >> "$CONFIG_FILE" + fi + + print_status "Configuration file created: $CONFIG_FILE" + + # Step 4: Add peers + print_header "Step 4: Add Peers" + + # Zion default peer information (from CURRENT_WORKING/zion.conf) + local ZION_PUBLIC_KEY="2ztJbrN1x1NWanzPGLiKL19ZkdOhm5Y7WeKEWBT5cyg=" + local ZION_ENDPOINT="ugh.im:51820" + local ZION_ALLOWED_IPS="10.8.0.0/24" + + # Try to load Zion config, fall back to hardcoded if not available + if ! load_zion_config; then + print_warning "Using hardcoded Zion configuration" + fi + + # Ask if user wants to add Zion as default peer (unless this IS Zion) + if [[ "$node_name" != "zion" ]]; then + echo "Zion is the central server for this network." + read -p "Do you want to add Zion as a peer? (y/n, default: y): " ADD_ZION + ADD_ZION=${ADD_ZION:-y} + + if [[ $ADD_ZION =~ ^[Yy]$ ]]; then + echo "" + echo "Adding Zion as peer with default settings:" + echo " Public Key: $ZION_PUBLIC_KEY" + echo " Endpoint: $ZION_ENDPOINT" + echo " Allowed IPs: $ZION_ALLOWED_IPS" + echo " Persistent Keepalive: 25" + echo "" + + # Add Zion peer to configuration + echo "" >> "$CONFIG_FILE" + echo "# Zion (central server)" >> "$CONFIG_FILE" + echo "[Peer]" >> "$CONFIG_FILE" + echo "PublicKey = $ZION_PUBLIC_KEY" >> "$CONFIG_FILE" + echo "AllowedIPs = $ZION_ALLOWED_IPS" >> "$CONFIG_FILE" + echo "Endpoint = $ZION_ENDPOINT" >> "$CONFIG_FILE" + echo "PersistentKeepalive = 25" >> "$CONFIG_FILE" + + print_status "Added Zion as default peer" + fi + fi + + # Add additional peers + while true; do + read -p "Do you want to add additional peers? (y/n): " ADD_PEER + if [[ ! $ADD_PEER =~ ^[Yy]$ ]]; then + break + fi + + echo "" + local peer_name + read -p "Enter peer name (e.g., aza, cth, galaxy): " peer_name + peer_name=$(safe_filename "$peer_name") + + local peer_public_key + while true; do + read -p "Enter peer public key: " peer_public_key + if validate_public_key "$peer_public_key"; then + break + else + print_error "Invalid WireGuard public key format" + fi + done + + local peer_allowed_ips + while true; do + read -p "Enter allowed IPs for this peer (e.g., 10.8.0.1/32 or 10.8.0.0/24): " peer_allowed_ips + if validate_ip "$peer_allowed_ips"; then + break + else + print_error "Invalid IP address format. Use format: x.x.x.x/y" + fi + done + + # Ask if this peer has an endpoint (for clients connecting to servers) + read -p "Does this peer have an endpoint? (y/n): " HAS_ENDPOINT + if [[ $HAS_ENDPOINT =~ ^[Yy]$ ]]; then + read -p "Enter endpoint (host:port): " PEER_ENDPOINT + local peer_keepalive + read -p "Enter persistent keepalive (seconds, default 25): " peer_keepalive + peer_keepalive=${peer_keepalive:-25} + fi + + # Add peer to configuration + echo "" >> "$CONFIG_FILE" + echo "# $peer_name" >> "$CONFIG_FILE" + echo "[Peer]" >> "$CONFIG_FILE" + echo "PublicKey = $peer_public_key" >> "$CONFIG_FILE" + echo "AllowedIPs = $peer_allowed_ips" >> "$CONFIG_FILE" + + if [[ $HAS_ENDPOINT =~ ^[Yy]$ ]]; then + echo "Endpoint = $PEER_ENDPOINT" >> "$CONFIG_FILE" + echo "PersistentKeepalive = $peer_keepalive" >> "$CONFIG_FILE" + fi + + print_status "Added peer: $peer_name" + done + + # Set proper file permissions + chmod 600 "$CONFIG_FILE" + print_status "Set file permissions to 600 (owner read/write only)" + + # Step 5: Display results + print_header "Step 5: Setup Complete" + + print_status "Configuration file created: $CONFIG_FILE" + print_status "Your public key: $PUBLIC_KEY" + echo "" + + # Zion update instructions + print_header "IMPORTANT: Update Zion Server Configuration" + echo "" + print_warning "You MUST add this peer to Zion's configuration file:" + echo " /etc/wireguard/wg0.conf" + echo "" + echo "Add the following peer section to Zion's config:" + echo "----------------------------------------" + echo "# $node_name" + echo "[Peer]" + echo "PublicKey = $PUBLIC_KEY" + echo "AllowedIPs = ${ip_address%/*}/32" + echo "----------------------------------------" + echo "" + + # Show Zion's current configuration structure + if ! load_zion_config; then + echo "Zion's current configuration structure:" + echo "----------------------------------------" + echo "[Interface]" + echo "Address = 10.8.0.1/24" + echo "ListenPort = 51820" + echo "PrivateKey = " + echo "PostUp = " + echo "PostDown = " + echo "" + echo "#Cth" + echo "[Peer]" + echo "PublicKey = NBktXKy1s0n2lIlIMODvOqKNwAtYdoZH5feKt5P43i0=" + echo "AllowedIPs = 10.8.0.10/32" + echo "" + echo "#Aza" + echo "[Peer]" + echo "PublicKey = qmTKA257DLOrfhk5Zw8RyRmBSonmm6epbloT0P0ZWDc=" + echo "AllowedIPs = 10.8.0.2/32" + echo "" + echo "# Add your peer section here:" + echo "# $node_name" + echo "# [Peer]" + echo "# PublicKey = $PUBLIC_KEY" + echo "# AllowedIPs = ${ip_address%/*}/32" + echo "----------------------------------------" + fi + + echo "" + print_warning "After updating Zion's config:" + echo "1. Save the file" + echo "2. Restart Zion's WireGuard: sudo systemctl restart wg-quick@wg0" + echo "3. Then start this node's WireGuard: sudo wg-quick up ${node_name}" + echo "" + + # Show the configuration file + echo "Your configuration file contents:" + echo "----------------------------------------" + cat "$CONFIG_FILE" + echo "----------------------------------------" + + # Create a summary file + SUMMARY_FILE="$OUTPUT_DIR/${node_name}_summary.txt" + if [ -f "$SUMMARY_FILE" ]; then + print_warning "Summary file '$SUMMARY_FILE' already exists and will be overwritten." + fi + cat > "$SUMMARY_FILE" << EOF +WireGuard Configuration Summary for $node_name +=============================================== + +Node Name: $node_name +IP Address: $ip_address +$(if [[ $IS_SERVER =~ ^[Yy]$ ]]; then echo "Listen Port: $listen_port"; fi) + +Your Public Key: $PUBLIC_KEY +Your Private Key: $PRIVATE_KEY + +Configuration File: $CONFIG_FILE + +Commands to use: +- Start: sudo wg-quick up ${node_name} +- Stop: sudo wg-quick down ${node_name} +- Status: sudo wg show + +ZION SERVER UPDATE REQUIRED: +=========================== +You MUST add this peer to Zion's configuration file (/etc/wireguard/wg0.conf): + +# $node_name +[Peer] +PublicKey = $PUBLIC_KEY +AllowedIPs = ${ip_address%/*}/32 + +After updating Zion's config: +1. Save the file +2. Restart Zion's WireGuard: sudo systemctl restart wg-quick@wg0 +3. Then start this node's WireGuard: sudo wg-quick up ${node_name} + +Remember to: +1. Keep your private key secure +2. Update Zion's configuration with your public key +3. Set proper file permissions: chmod 600 $CONFIG_FILE +4. Restart both Zion and this node after configuration changes +EOF + + print_status "Summary saved to: $SUMMARY_FILE" +} + +# Function to show usage +show_usage() { + echo "Usage: $0 [OPTIONS]" + echo "" + echo "Options:" + echo " -h, --help Show this help message" + echo " -d, --dir DIR Set output directory (default: wireguard_configs)" + echo " -c, --config Use custom Zion config file" + echo "" + echo "This script will interactively guide you through creating WireGuard" + echo "keys and configuration files." +} + +# Parse command line arguments +while [[ $# -gt 0 ]]; do + case $1 in + -h|--help) + show_usage + exit 0 + ;; + -d|--dir) + OUTPUT_DIR="$2" + shift 2 + ;; + -c|--config) + ZION_CONFIG_FILE="$2" + shift 2 + ;; + *) + print_error "Unknown option: $1" + show_usage + exit 1 + ;; + esac +done + +# Run main function +main "$@" \ No newline at end of file