init commit
This commit is contained in:
@@ -0,0 +1,274 @@
|
||||
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/<interface>.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-dir>/<iface>.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 <name>' 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.../<hidden>/).
|
||||
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] + " <hidden>"
|
||||
}
|
||||
}
|
||||
}
|
||||
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")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user