package main import ( "bufio" "fmt" "os" "strings" ) // initServer runs the interactive server setup. Unlike the original script it // no longer installs dependencies — that is a separate, prerequisite step. It // verifies the deps-installed flag recorded by `install-deps` and aborts if it // is missing, then generates keys/obfuscation params, writes configs, enables // forwarding and starts the service. func initServer() { osInfo := detectOS() // New flow: dependency installation must have happened first. st := loadState() if !st.DepsInstalled { die("Dependencies not installed — run 'install-deps' first, then re-run 'init-server'") } info("Dependency check passed (installed %s on %s %s)", valueOr(st.InstalledAt, "unknown time"), valueOr(st.OSID, "?"), valueOr(st.OSVersion, "?")) // The awg binary must exist now that deps are installed. requireBinary("awg") fmt.Println() fmt.Println("=== AmneziaWG Server Initialization ===") fmt.Println() fmt.Println("Dependencies already installed. This will:") fmt.Println(" 1. Generate server key pair") fmt.Println(" 2. Generate AmneziaWG obfuscation parameters") fmt.Printf(" 3. Write %s/.conf\n", awgConfDir) fmt.Println(" 4. Enable IP forwarding (persistent)") fmt.Println(" 5. Start and enable the AmneziaWG service") fmt.Println() if !confirm("Proceed?") { info("Aborted") os.Exit(0) } fmt.Println() c := &Config{} // Interactive configuration. c.Interface = promptDefault("AmneziaWG interface name", "awg0") c.Network = promptDefault("VPN network CIDR", "10.0.0.0/24") requireSlash24(c.Network) c.Port = promptDefault("Listen port", "51820") detectedIP := detectPublicIP() c.PublicIP = promptDefault("Server public IP or hostname", detectedIP) c.DNS = promptDefault("DNS server for clients", "1.1.1.1") c.MTU = promptDefault("MTU", "1420") mtu := atoiOrDie(c.MTU) // AmneziaWG obfuscation parameters — randomised defaults, editable. // Ceilings are derived from the interface MTU per the AmneziaWG spec: // Jmin < Jmax <= MTU ; S1 <= MTU-148 ; S2 <= MTU-92 mtuJunkCeil := mtu mtuS1Ceil := mtu - 148 mtuS2Ceil := mtu - 92 rndJc := randRange(4, 12) jminHi := minInt(mtuJunkCeil-2, 32) rndJmin := randRange(8, jminHi) jmaxLo := maxInt(rndJmin+32, 80) jmaxHi := minInt(mtuJunkCeil, 200) rndJmax := randRange(jmaxLo, jmaxHi) rndS1 := randRange(15, minInt(mtuS1Ceil, 150)) rndS2 := randRange(15, minInt(mtuS2Ceil, 150)) // S1 + 56 != S2 (spec constraint) — resample S2 until it holds. for rndS1+56 == rndS2 { rndS2 = randRange(15, minInt(mtuS2Ceil, 150)) } fmt.Println() fmt.Println("AmneziaWG obfuscation parameters (shared by server and all clients):") c.Jc = promptDefault("Jc (junk packet count)", itoa(rndJc)) c.Jmin = promptDefault("Jmin (min junk size)", itoa(rndJmin)) c.Jmax = promptDefault("Jmax (max junk size)", itoa(rndJmax)) c.S1 = promptDefault("S1 (init junk size)", itoa(rndS1)) c.S2 = promptDefault("S2 (response junk size)", itoa(rndS2)) // Four distinct magic-header values in [5, 2^31-1]. h1 := randMagic() h2 := randMagic() for h2 == h1 { h2 = randMagic() } h3 := randMagic() for h3 == h1 || h3 == h2 { h3 = randMagic() } h4 := randMagic() for h4 == h1 || h4 == h2 || h4 == h3 { h4 = randMagic() } c.H1 = promptDefault("H1 (magic header 1)", itoa64(h1)) c.H2 = promptDefault("H2 (magic header 2)", itoa64(h2)) c.H3 = promptDefault("H3 (magic header 3)", itoa64(h3)) c.H4 = promptDefault("H4 (magic header 4)", itoa64(h4)) fmt.Println() // Generate server keys (awg is guaranteed present by the deps check). info("Generating server keys...") priv, err := output("awg", "genkey") if err != nil { die("awg genkey failed: %v", err) } pub, err := outputWithInput(priv, "awg", "pubkey") if err != nil { die("awg pubkey failed: %v", err) } c.ServerPriv = priv c.ServerPub = pub srvIP := serverIP(c.Network) defIface := defaultRouteIface() // Write the profiler config file. writeConfig(c) // Write /.conf and companion nft ruleset. confPath := awgConfPath(c.Interface) info("Writing %s...", confPath) if err := os.MkdirAll(awgConfDir, 0755); err != nil { die("Failed to create %s: %v", awgConfDir, err) } if err := os.WriteFile(confPath, []byte(awgConfHeader(c, c.ServerPriv, srvIP, c.Port, c.MTU)), 0600); err != nil { die("Failed to write %s: %v", confPath, err) } writeNftRules(c.Interface, defIface, c.MTU) // IP forwarding. info("Enabling IP forwarding...") enableIPForwarding() // Service. serviceEnable(osInfo, c.Interface) serviceStart(osInfo, c.Interface) // Init client storage. initStorage() fmt.Println() info("=== Server initialization complete ===") info("Interface : %s", c.Interface) info("Server IP : %s", srvIP) info("Network : %s", c.Network) info("Port : %s", c.Port) info("Public IP : %s", c.PublicIP) info("Public Key: %s", c.ServerPub) info("Obfusc. : Jc=%s Jmin=%s Jmax=%s S1=%s S2=%s", c.Jc, c.Jmin, c.Jmax, c.S1, c.S2) fmt.Println() info("Next steps: use 'create ' to add VPN clients") } // ─── server management ─────────────────────────────────────────────────────────── func serverStatus(c *Config, osInfo *OSInfo) { fmt.Println() fmt.Println("=== AmneziaWG Server Configuration ===") fmt.Printf(" %-12s %s\n", "Interface:", c.Interface) fmt.Printf(" %-12s %s\n", "Network:", c.Network) fmt.Printf(" %-12s %s\n", "Port:", c.Port) fmt.Printf(" %-12s %s\n", "Public IP:", c.PublicIP) fmt.Printf(" %-12s %s\n", "Public Key:", c.ServerPub) fmt.Println() fmt.Println("=== Interface Status ===") if awgIfaceUp(c.Interface) { run("awg", "show", c.Interface) } else { fmt.Printf(" Interface %s is DOWN\n", c.Interface) } fmt.Println() fmt.Println("=== Service Status ===") serviceStatus(osInfo, c.Interface) } func showConfig() { c := loadConfig() confPath := awgConfPath(c.Interface) fmt.Println() fmt.Printf("=== Profiler Config (%s) ===\n", configFile) printFilteredConfig(configFile) fmt.Println() if data, err := os.ReadFile(confPath); err == nil { fmt.Printf("=== AmneziaWG Config (%s) ===\n", confPath) fmt.Print(hidePrivateKey(string(data))) } else { warn("%s not found", confPath) } } // printFilteredConfig reproduces the bash grep chain: drop lines containing // PRIVATE / PASSW / KEY, comment lines, and blank lines. func printFilteredConfig(path string) { f, err := os.Open(path) if err != nil { return } defer f.Close() scanner := bufio.NewScanner(f) for scanner.Scan() { line := scanner.Text() if strings.Contains(line, "PRIVATE") || strings.Contains(line, "PASSW") || strings.Contains(line, "KEY") { continue } if strings.HasPrefix(line, "#") || strings.TrimSpace(line) == "" { continue } fmt.Println(line) } } // hidePrivateKey masks the PrivateKey value (bash: sed s/PrivateKey...//). func hidePrivateKey(content string) string { lines := strings.Split(content, "\n") for i, line := range lines { trimmed := strings.TrimSpace(line) if strings.HasPrefix(trimmed, "PrivateKey") { if idx := strings.Index(line, "="); idx >= 0 { lines[i] = line[:idx+1] + " " } } } return strings.Join(lines, "\n") } // syncConfig regenerates the interface conf with all ACTIVE peers, optionally // restarting the service. func syncConfig(c *Config, osInfo *OSInfo) { confPath := awgConfPath(c.Interface) if _, err := os.Stat(confPath); err != nil { die("%s not found — run init-server first", confPath) } srvIP := serverIP(c.Network) defIface := defaultRouteIface() mtu := c.mtuOr("1420") info("Regenerating %s with all ACTIVE peers...", confPath) clients := loadRegistry() var b strings.Builder b.WriteString(awgConfHeader(c, c.ServerPriv, srvIP, c.Port, mtu)) for _, cl := range clients { if cl.IsEnabled == "ACTIVE" { b.WriteString(peerBlock(cl.Name, cl.PublicKey, cl.PSKKey, cl.IP)) } } if err := os.WriteFile(confPath, []byte(b.String()), 0600); err != nil { die("Failed to write %s: %v", confPath, err) } writeNftRules(c.Interface, defIface, mtu) info("Config written: %s", confPath) if confirm("Restart AmneziaWG to apply changes?") { serviceRestart(osInfo, c.Interface) info("AmneziaWG restarted") } }