631 lines
16 KiB
Go
631 lines
16 KiB
Go
|
|
package setup
|
||
|
|
|
||
|
|
import (
|
||
|
|
"bufio"
|
||
|
|
"fmt"
|
||
|
|
"net"
|
||
|
|
"os"
|
||
|
|
"os/exec"
|
||
|
|
"path/filepath"
|
||
|
|
"sort"
|
||
|
|
"strconv"
|
||
|
|
"strings"
|
||
|
|
|
||
|
|
"nano-router/auth"
|
||
|
|
"nano-router/config"
|
||
|
|
"nano-router/network"
|
||
|
|
)
|
||
|
|
|
||
|
|
const serviceName = "network-manager"
|
||
|
|
const servicePath = "/etc/init.d/" + serviceName
|
||
|
|
const binInstallPath = "/usr/local/bin/network-manager"
|
||
|
|
|
||
|
|
func Run() {
|
||
|
|
reader := bufio.NewReader(os.Stdin)
|
||
|
|
|
||
|
|
printBanner()
|
||
|
|
fmt.Println()
|
||
|
|
fmt.Println(" This wizard will guide you through initial router configuration.")
|
||
|
|
fmt.Println(" Press Enter to accept the default value shown in brackets.")
|
||
|
|
fmt.Println()
|
||
|
|
|
||
|
|
ifaces, err := network.GetInterfaces()
|
||
|
|
if err != nil {
|
||
|
|
fmt.Fprintf(os.Stderr, " [ERROR] Cannot list network interfaces: %v\n", err)
|
||
|
|
os.Exit(1)
|
||
|
|
}
|
||
|
|
|
||
|
|
physicalIfaces := filterPhysicalIfaces(ifaces)
|
||
|
|
if len(physicalIfaces) < 2 {
|
||
|
|
fmt.Fprintf(os.Stderr, " [ERROR] At least 2 physical interfaces are required, found %d.\n", len(physicalIfaces))
|
||
|
|
os.Exit(1)
|
||
|
|
}
|
||
|
|
|
||
|
|
fmt.Println(" ── Step 1: WAN Interface ──────────────────────────────────────────")
|
||
|
|
fmt.Println()
|
||
|
|
|
||
|
|
wanIface := selectInterface(reader, physicalIfaces, "Select WAN interface")
|
||
|
|
fmt.Println()
|
||
|
|
|
||
|
|
fmt.Printf(" Configuring WAN interface: %s\n", wanIface)
|
||
|
|
fmt.Println()
|
||
|
|
|
||
|
|
wanMode := selectWANMode(reader)
|
||
|
|
fmt.Println()
|
||
|
|
|
||
|
|
var wanAddress, wanNetmask, wanGateway string
|
||
|
|
if wanMode == "static" {
|
||
|
|
wanStats, _ := network.GetInterfaceStats(wanIface)
|
||
|
|
defAddr := ""
|
||
|
|
if wanStats != nil && wanStats.IPv4 != "" {
|
||
|
|
defAddr = wanStats.IPv4
|
||
|
|
}
|
||
|
|
defMask := "255.255.255.0"
|
||
|
|
if wanStats != nil && wanStats.IPv4Mask != "" {
|
||
|
|
defMask = wanStats.IPv4Mask
|
||
|
|
}
|
||
|
|
defGW := ""
|
||
|
|
if wanStats != nil && wanStats.Gateway != "" {
|
||
|
|
defGW = wanStats.Gateway
|
||
|
|
}
|
||
|
|
|
||
|
|
wanAddress = readIP(reader, " WAN IP address", defAddr)
|
||
|
|
wanNetmask = readNetmask(reader, " WAN netmask", defMask)
|
||
|
|
wanGateway = readIPOrDefault(reader, " WAN gateway", defGW)
|
||
|
|
}
|
||
|
|
|
||
|
|
fmt.Println()
|
||
|
|
fmt.Println(" ── Step 2: LAN Interface ──────────────────────────────────────────")
|
||
|
|
fmt.Println()
|
||
|
|
|
||
|
|
remainingIfaces := removeItem(physicalIfaces, wanIface)
|
||
|
|
lanIface := selectInterface(reader, remainingIfaces, "Select LAN interface")
|
||
|
|
fmt.Println()
|
||
|
|
|
||
|
|
lanStats, _ := network.GetInterfaceStats(lanIface)
|
||
|
|
|
||
|
|
defLanAddr := "192.168.1.1"
|
||
|
|
defLanMask := "255.255.255.0"
|
||
|
|
if lanStats != nil && lanStats.IPv4 != "" {
|
||
|
|
defLanAddr = lanStats.IPv4
|
||
|
|
}
|
||
|
|
if lanStats != nil && lanStats.IPv4Mask != "" {
|
||
|
|
defLanMask = lanStats.IPv4Mask
|
||
|
|
}
|
||
|
|
|
||
|
|
fmt.Printf(" Configuring LAN interface: %s\n", lanIface)
|
||
|
|
fmt.Println()
|
||
|
|
|
||
|
|
lanAddress := readIP(reader, " LAN IP address", defLanAddr)
|
||
|
|
lanNetmask := readNetmask(reader, " LAN netmask", defLanMask)
|
||
|
|
|
||
|
|
lanSubnet := computeSubnet(lanAddress, lanNetmask)
|
||
|
|
|
||
|
|
fmt.Println()
|
||
|
|
|
||
|
|
enableDHCP := readYesNo(reader, " Enable DHCP server on LAN?", true)
|
||
|
|
fmt.Println()
|
||
|
|
|
||
|
|
var dhcpRangeStart, dhcpRangeEnd string
|
||
|
|
var dhcpLeaseTime int
|
||
|
|
enableDHCPPool := false
|
||
|
|
if enableDHCP {
|
||
|
|
defStart := incrementIP(lanAddress, 100)
|
||
|
|
defEnd := incrementIP(lanAddress, 200)
|
||
|
|
if lanStats != nil && lanStats.IPv4 != "" {
|
||
|
|
defStart = incrementIP(lanStats.IPv4, 100)
|
||
|
|
defEnd = incrementIP(lanStats.IPv4, 200)
|
||
|
|
}
|
||
|
|
|
||
|
|
fmt.Println(" ── DHCP Pool Configuration ─────────────────────────────────────────")
|
||
|
|
fmt.Println()
|
||
|
|
dhcpRangeStart = readIP(reader, " DHCP range start", defStart)
|
||
|
|
dhcpRangeEnd = readIP(reader, " DHCP range end", defEnd)
|
||
|
|
dhcpLeaseTime = readInt(reader, " Lease time (seconds)", 86400)
|
||
|
|
enableDHCPPool = true
|
||
|
|
fmt.Println()
|
||
|
|
}
|
||
|
|
|
||
|
|
fmt.Println(" ── Step 3: Administrator Account ──────────────────────────────────")
|
||
|
|
fmt.Println()
|
||
|
|
|
||
|
|
adminUsername := readNonEmpty(reader, " Administrator login", "admin")
|
||
|
|
adminPassword := readPassword(reader, " Administrator password")
|
||
|
|
fmt.Println()
|
||
|
|
|
||
|
|
fmt.Println()
|
||
|
|
fmt.Println(" ── Generating Configuration ────────────────────────────────────────")
|
||
|
|
fmt.Println()
|
||
|
|
|
||
|
|
cfg := buildConfig(wanIface, wanMode, wanAddress, wanNetmask, wanGateway,
|
||
|
|
lanIface, lanAddress, lanNetmask, lanSubnet,
|
||
|
|
enableDHCP, enableDHCPPool, dhcpRangeStart, dhcpRangeEnd, dhcpLeaseTime,
|
||
|
|
adminUsername, adminPassword)
|
||
|
|
|
||
|
|
if err := config.Save(cfg); err != nil {
|
||
|
|
fmt.Fprintf(os.Stderr, " [ERROR] Cannot save config: %v\n", err)
|
||
|
|
os.Exit(1)
|
||
|
|
}
|
||
|
|
fmt.Printf(" Configuration saved to %s\n", config.GetPath())
|
||
|
|
fmt.Println()
|
||
|
|
|
||
|
|
fmt.Println(" ── Installing Service ──────────────────────────────────────────────")
|
||
|
|
fmt.Println()
|
||
|
|
|
||
|
|
if err := installService(); err != nil {
|
||
|
|
fmt.Fprintf(os.Stderr, " [ERROR] Cannot install service: %v\n", err)
|
||
|
|
os.Exit(1)
|
||
|
|
}
|
||
|
|
fmt.Println()
|
||
|
|
|
||
|
|
fmt.Println(" ── Setup Complete ──────────────────────────────────────────────────")
|
||
|
|
fmt.Println()
|
||
|
|
fmt.Printf(" Admin panel: http://%s:8080\n", lanAddress)
|
||
|
|
fmt.Printf(" Login: %s\n", adminUsername)
|
||
|
|
fmt.Printf(" Password: %s\n", adminPassword)
|
||
|
|
fmt.Println()
|
||
|
|
fmt.Println(" The service has been installed and enabled for autostart.")
|
||
|
|
fmt.Println(" Run 'rc-service network-manager start' or reboot to activate.")
|
||
|
|
fmt.Println()
|
||
|
|
}
|
||
|
|
|
||
|
|
func printBanner() {
|
||
|
|
fmt.Println()
|
||
|
|
fmt.Println(" ┌──────────────────────────────────────────────────────────────────┐")
|
||
|
|
fmt.Println(" │ Alpine Router -- Initial Setup Wizard │")
|
||
|
|
fmt.Println(" └──────────────────────────────────────────────────────────────────┘")
|
||
|
|
}
|
||
|
|
|
||
|
|
func selectInterface(reader *bufio.Reader, ifaces []string, prompt string) string {
|
||
|
|
fmt.Printf(" %s:\n", prompt)
|
||
|
|
fmt.Println()
|
||
|
|
for i, name := range ifaces {
|
||
|
|
stats, _ := network.GetInterfaceStats(name)
|
||
|
|
extra := ""
|
||
|
|
if stats != nil {
|
||
|
|
parts := []string{}
|
||
|
|
if stats.IPv4 != "" {
|
||
|
|
parts = append(parts, stats.IPv4)
|
||
|
|
}
|
||
|
|
if stats.State != "" {
|
||
|
|
parts = append(parts, "["+stats.State+"]")
|
||
|
|
}
|
||
|
|
if len(parts) > 0 {
|
||
|
|
extra = " " + strings.Join(parts, " ")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
fmt.Printf(" %2d) %s%s\n", i+1, name, extra)
|
||
|
|
}
|
||
|
|
fmt.Println()
|
||
|
|
|
||
|
|
for {
|
||
|
|
fmt.Print(" Enter number: ")
|
||
|
|
input := strings.TrimSpace(readLine(reader))
|
||
|
|
num, err := strconv.Atoi(input)
|
||
|
|
if err != nil || num < 1 || num > len(ifaces) {
|
||
|
|
fmt.Printf(" [!] Please enter a number between 1 and %d\n", len(ifaces))
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
selected := ifaces[num-1]
|
||
|
|
fmt.Printf(" Selected: %s\n", selected)
|
||
|
|
return selected
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func selectWANMode(reader *bufio.Reader) string {
|
||
|
|
fmt.Println(" WAN addressing mode:")
|
||
|
|
fmt.Println()
|
||
|
|
fmt.Println(" 1) DHCP ( automatic )")
|
||
|
|
fmt.Println(" 2) Static ( manual IP configuration )")
|
||
|
|
fmt.Println()
|
||
|
|
|
||
|
|
for {
|
||
|
|
fmt.Print(" Select mode (1-2) [1]: ")
|
||
|
|
input := strings.TrimSpace(readLine(reader))
|
||
|
|
if input == "" || input == "1" {
|
||
|
|
fmt.Println(" Selected: DHCP")
|
||
|
|
return "dhcp"
|
||
|
|
}
|
||
|
|
if input == "2" {
|
||
|
|
fmt.Println(" Selected: Static")
|
||
|
|
return "static"
|
||
|
|
}
|
||
|
|
fmt.Println(" [!] Enter 1 or 2")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func readIP(reader *bufio.Reader, prompt, def string) string {
|
||
|
|
for {
|
||
|
|
promptStr := fmt.Sprintf(" %s", prompt)
|
||
|
|
if def != "" {
|
||
|
|
promptStr += fmt.Sprintf(" [%s]", def)
|
||
|
|
}
|
||
|
|
promptStr += ": "
|
||
|
|
fmt.Print(promptStr)
|
||
|
|
input := strings.TrimSpace(readLine(reader))
|
||
|
|
if input == "" && def != "" {
|
||
|
|
input = def
|
||
|
|
}
|
||
|
|
if net.ParseIP(input) == nil {
|
||
|
|
fmt.Println(" [!] Invalid IP address, try again")
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
return input
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func readIPOrDefault(reader *bufio.Reader, prompt, def string) string {
|
||
|
|
for {
|
||
|
|
promptStr := fmt.Sprintf(" %s", prompt)
|
||
|
|
if def != "" {
|
||
|
|
promptStr += fmt.Sprintf(" [%s]", def)
|
||
|
|
}
|
||
|
|
promptStr += " (or leave empty to skip): "
|
||
|
|
fmt.Print(promptStr)
|
||
|
|
input := strings.TrimSpace(readLine(reader))
|
||
|
|
if input == "" {
|
||
|
|
if def != "" {
|
||
|
|
return def
|
||
|
|
}
|
||
|
|
return ""
|
||
|
|
}
|
||
|
|
if net.ParseIP(input) == nil {
|
||
|
|
fmt.Println(" [!] Invalid IP address, try again")
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
return input
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func readNetmask(reader *bufio.Reader, prompt, def string) string {
|
||
|
|
for {
|
||
|
|
promptStr := fmt.Sprintf(" %s", prompt)
|
||
|
|
if def != "" {
|
||
|
|
promptStr += fmt.Sprintf(" [%s]", def)
|
||
|
|
}
|
||
|
|
promptStr += ": "
|
||
|
|
fmt.Print(promptStr)
|
||
|
|
input := strings.TrimSpace(readLine(reader))
|
||
|
|
if input == "" && def != "" {
|
||
|
|
input = def
|
||
|
|
}
|
||
|
|
if isValidNetmask(input) {
|
||
|
|
return input
|
||
|
|
}
|
||
|
|
fmt.Println(" [!] Invalid netmask, try again (e.g. 255.255.255.0)")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func readYesNo(reader *bufio.Reader, prompt string, def bool) bool {
|
||
|
|
defStr := "Y/n"
|
||
|
|
if !def {
|
||
|
|
defStr = "y/N"
|
||
|
|
}
|
||
|
|
for {
|
||
|
|
fmt.Printf(" %s [%s]: ", prompt, defStr)
|
||
|
|
input := strings.TrimSpace(strings.ToLower(readLine(reader)))
|
||
|
|
if input == "" {
|
||
|
|
return def
|
||
|
|
}
|
||
|
|
switch input {
|
||
|
|
case "y", "yes":
|
||
|
|
return true
|
||
|
|
case "n", "no":
|
||
|
|
return false
|
||
|
|
default:
|
||
|
|
fmt.Println(" [!] Enter y or n")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func readNonEmpty(reader *bufio.Reader, prompt, def string) string {
|
||
|
|
for {
|
||
|
|
promptStr := fmt.Sprintf(" %s", prompt)
|
||
|
|
if def != "" {
|
||
|
|
promptStr += fmt.Sprintf(" [%s]", def)
|
||
|
|
}
|
||
|
|
promptStr += ": "
|
||
|
|
fmt.Print(promptStr)
|
||
|
|
input := strings.TrimSpace(readLine(reader))
|
||
|
|
if input == "" && def != "" {
|
||
|
|
return def
|
||
|
|
}
|
||
|
|
if input != "" {
|
||
|
|
return input
|
||
|
|
}
|
||
|
|
fmt.Println(" [!] Value cannot be empty")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func readPassword(reader *bufio.Reader, prompt string) string {
|
||
|
|
fd := int(os.Stdin.Fd())
|
||
|
|
oldState, canMute := termiosGetState(fd)
|
||
|
|
|
||
|
|
for {
|
||
|
|
fmt.Printf(" %s: ", prompt)
|
||
|
|
if canMute {
|
||
|
|
termiosDisableEcho(fd)
|
||
|
|
}
|
||
|
|
pw1 := readLineRaw()
|
||
|
|
if canMute {
|
||
|
|
termiosRestore(fd, oldState)
|
||
|
|
fmt.Println()
|
||
|
|
}
|
||
|
|
if len(pw1) < 4 {
|
||
|
|
fmt.Println(" [!] Password must be at least 4 characters")
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
|
||
|
|
fmt.Print(" Confirm password: ")
|
||
|
|
if canMute {
|
||
|
|
termiosDisableEcho(fd)
|
||
|
|
}
|
||
|
|
pw2 := readLineRaw()
|
||
|
|
if canMute {
|
||
|
|
termiosRestore(fd, oldState)
|
||
|
|
fmt.Println()
|
||
|
|
}
|
||
|
|
if pw1 != pw2 {
|
||
|
|
fmt.Println(" [!] Passwords do not match, try again")
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
return pw1
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func readLineRaw() string {
|
||
|
|
var line []byte
|
||
|
|
buf := make([]byte, 1)
|
||
|
|
for {
|
||
|
|
n, err := os.Stdin.Read(buf)
|
||
|
|
if n > 0 {
|
||
|
|
if buf[0] == '\n' || buf[0] == '\r' {
|
||
|
|
break
|
||
|
|
}
|
||
|
|
line = append(line, buf[0])
|
||
|
|
}
|
||
|
|
if err != nil {
|
||
|
|
break
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return string(line)
|
||
|
|
}
|
||
|
|
|
||
|
|
func readInt(reader *bufio.Reader, prompt string, def int) int {
|
||
|
|
for {
|
||
|
|
fmt.Printf(" %s [%d]: ", prompt, def)
|
||
|
|
input := strings.TrimSpace(readLine(reader))
|
||
|
|
if input == "" {
|
||
|
|
return def
|
||
|
|
}
|
||
|
|
num, err := strconv.Atoi(input)
|
||
|
|
if err != nil || num <= 0 {
|
||
|
|
fmt.Println(" [!] Enter a positive integer")
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
return num
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func readLine(reader *bufio.Reader) string {
|
||
|
|
line, _ := reader.ReadString('\n')
|
||
|
|
return strings.TrimRight(line, "\r\n")
|
||
|
|
}
|
||
|
|
|
||
|
|
func filterPhysicalIfaces(ifaces []string) []string {
|
||
|
|
skip := map[string]bool{"lo": true}
|
||
|
|
var result []string
|
||
|
|
for _, name := range ifaces {
|
||
|
|
if skip[name] {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
if network.IsVLAN(name) {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
result = append(result, name)
|
||
|
|
}
|
||
|
|
sort.Strings(result)
|
||
|
|
return result
|
||
|
|
}
|
||
|
|
|
||
|
|
func removeItem(xs []string, item string) []string {
|
||
|
|
var result []string
|
||
|
|
for _, x := range xs {
|
||
|
|
if x != item {
|
||
|
|
result = append(result, x)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return result
|
||
|
|
}
|
||
|
|
|
||
|
|
func isValidNetmask(mask string) bool {
|
||
|
|
m := net.ParseIP(mask)
|
||
|
|
if m == nil {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
m4 := m.To4()
|
||
|
|
if m4 == nil {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
var n uint32
|
||
|
|
for _, b := range m4 {
|
||
|
|
n = (n << 8) | uint32(b)
|
||
|
|
}
|
||
|
|
if n == 0 {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
complement := ^n
|
||
|
|
return complement&((complement)+1) == 0
|
||
|
|
}
|
||
|
|
|
||
|
|
func computeSubnet(ipStr, maskStr string) string {
|
||
|
|
ip := net.ParseIP(ipStr)
|
||
|
|
mask := net.ParseIP(maskStr)
|
||
|
|
if ip == nil || mask == nil {
|
||
|
|
return ipStr
|
||
|
|
}
|
||
|
|
ip4 := ip.To4()
|
||
|
|
mask4 := mask.To4()
|
||
|
|
if ip4 == nil || mask4 == nil {
|
||
|
|
return ipStr
|
||
|
|
}
|
||
|
|
ipMask := net.IPMask(mask4)
|
||
|
|
subnet := net.IPNet{
|
||
|
|
IP: ip4.Mask(ipMask),
|
||
|
|
Mask: ipMask,
|
||
|
|
}
|
||
|
|
return subnet.String()
|
||
|
|
}
|
||
|
|
|
||
|
|
func incrementIP(ipStr string, offset int) string {
|
||
|
|
ip := net.ParseIP(ipStr)
|
||
|
|
if ip == nil {
|
||
|
|
return ipStr
|
||
|
|
}
|
||
|
|
ip4 := ip.To4()
|
||
|
|
if ip4 == nil {
|
||
|
|
return ipStr
|
||
|
|
}
|
||
|
|
val := uint32(ip4[0])<<24 | uint32(ip4[1])<<16 | uint32(ip4[2])<<8 | uint32(ip4[3])
|
||
|
|
val += uint32(offset)
|
||
|
|
return fmt.Sprintf("%d.%d.%d.%d",
|
||
|
|
(val>>24)&0xFF, (val>>16)&0xFF, (val>>8)&0xFF, val&0xFF)
|
||
|
|
}
|
||
|
|
|
||
|
|
func buildConfig(
|
||
|
|
wanIface, wanMode, wanAddress, wanNetmask, wanGateway,
|
||
|
|
lanIface, lanAddress, lanNetmask, lanSubnet string,
|
||
|
|
enableDHCP, enableDHCPPool bool,
|
||
|
|
dhcpRangeStart, dhcpRangeEnd string,
|
||
|
|
dhcpLeaseTime int,
|
||
|
|
adminUsername, adminPassword string,
|
||
|
|
) *config.AppConfig {
|
||
|
|
cfg := &config.AppConfig{
|
||
|
|
Interfaces: map[string]*config.InterfaceConfig{
|
||
|
|
wanIface: {
|
||
|
|
Type: "wan",
|
||
|
|
Auto: true,
|
||
|
|
Mode: wanMode,
|
||
|
|
Address: wanAddress,
|
||
|
|
Netmask: wanNetmask,
|
||
|
|
Gateway: wanGateway,
|
||
|
|
},
|
||
|
|
lanIface: {
|
||
|
|
Type: "lan",
|
||
|
|
Auto: true,
|
||
|
|
Mode: "static",
|
||
|
|
Address: lanAddress,
|
||
|
|
Netmask: lanNetmask,
|
||
|
|
},
|
||
|
|
},
|
||
|
|
NAT: config.NATConfig{
|
||
|
|
Interfaces: []string{lanIface},
|
||
|
|
},
|
||
|
|
Firewall: config.FirewallConfig{
|
||
|
|
VLANIsolation: true,
|
||
|
|
Rules: []config.FirewallRule{},
|
||
|
|
},
|
||
|
|
DHCP: config.DHCPConfig{
|
||
|
|
Enabled: enableDHCP,
|
||
|
|
Pools: []config.DHCPPool{},
|
||
|
|
},
|
||
|
|
KnownDevices: []config.KnownDevice{},
|
||
|
|
Mihomo: config.MihomoConfig{Enabled: false},
|
||
|
|
ClientPolicy: config.ClientPolicyConfig{Default: "direct"},
|
||
|
|
Auth: config.AuthConfig{
|
||
|
|
Username: adminUsername,
|
||
|
|
PasswordHash: auth.HashPassword(adminPassword),
|
||
|
|
},
|
||
|
|
ListenAddresses: []string{lanAddress},
|
||
|
|
}
|
||
|
|
|
||
|
|
if enableDHCP && enableDHCPPool {
|
||
|
|
parts := strings.Split(lanSubnet, "/")
|
||
|
|
subnetPart := lanSubnet
|
||
|
|
if len(parts) == 2 {
|
||
|
|
subnetPart = parts[0] + "/" + parts[1]
|
||
|
|
}
|
||
|
|
|
||
|
|
dnsServers := []string{lanAddress}
|
||
|
|
if wanMode == "static" && wanGateway != "" {
|
||
|
|
dnsServers = []string{lanAddress, wanGateway}
|
||
|
|
}
|
||
|
|
|
||
|
|
cfg.DHCP.Pools = append(cfg.DHCP.Pools, config.DHCPPool{
|
||
|
|
Interface: lanIface,
|
||
|
|
Enabled: true,
|
||
|
|
Subnet: subnetPart,
|
||
|
|
Netmask: lanNetmask,
|
||
|
|
RangeStart: dhcpRangeStart,
|
||
|
|
RangeEnd: dhcpRangeEnd,
|
||
|
|
Router: lanAddress,
|
||
|
|
DNS: dnsServers,
|
||
|
|
LeaseTime: dhcpLeaseTime,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
return cfg
|
||
|
|
}
|
||
|
|
|
||
|
|
func installService() error {
|
||
|
|
exe, err := os.Executable()
|
||
|
|
if err != nil {
|
||
|
|
return fmt.Errorf("detect executable path: %w", err)
|
||
|
|
}
|
||
|
|
resolved, err := filepath.EvalSymlinks(exe)
|
||
|
|
if err != nil {
|
||
|
|
resolved = exe
|
||
|
|
}
|
||
|
|
|
||
|
|
if _, err := os.Stat(servicePath); err == nil {
|
||
|
|
fmt.Println(" Removing existing service...")
|
||
|
|
_ = exec.Command("rc-service", serviceName, "stop").Run()
|
||
|
|
_ = exec.Command("rc-update", "delete", serviceName).Run()
|
||
|
|
_ = os.Remove(servicePath)
|
||
|
|
}
|
||
|
|
|
||
|
|
fmt.Printf(" Installing binary to %s...\n", binInstallPath)
|
||
|
|
if err := os.MkdirAll(filepath.Dir(binInstallPath), 0755); err != nil {
|
||
|
|
return fmt.Errorf("create bin directory: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
if out, err := exec.Command("cp", resolved, binInstallPath).CombinedOutput(); err != nil {
|
||
|
|
return fmt.Errorf("copy binary: %s: %w", strings.TrimSpace(string(out)), err)
|
||
|
|
}
|
||
|
|
|
||
|
|
if err := os.Chmod(binInstallPath, 0755); err != nil {
|
||
|
|
return fmt.Errorf("chmod binary: %w", err)
|
||
|
|
}
|
||
|
|
fmt.Println(" Binary installed and made executable")
|
||
|
|
|
||
|
|
serviceContent := `#!/sbin/openrc-run
|
||
|
|
|
||
|
|
description="Network Manager Web Panel"
|
||
|
|
command="` + binInstallPath + `"
|
||
|
|
command_background=true
|
||
|
|
pidfile="/run/${RC_SVCNAME}.pid"
|
||
|
|
output_log="/var/log/network-manager.log"
|
||
|
|
error_log="/var/log/network-manager.log"
|
||
|
|
|
||
|
|
depend() {
|
||
|
|
need net
|
||
|
|
after firewall
|
||
|
|
}
|
||
|
|
|
||
|
|
start_pre() {
|
||
|
|
checkpath --directory /var/log
|
||
|
|
}
|
||
|
|
`
|
||
|
|
|
||
|
|
if err := os.WriteFile(servicePath, []byte(serviceContent), 0755); err != nil {
|
||
|
|
return fmt.Errorf("write service file: %w", err)
|
||
|
|
}
|
||
|
|
fmt.Printf(" Service file created: %s\n", servicePath)
|
||
|
|
|
||
|
|
if out, err := exec.Command("rc-update", "add", serviceName, "default").CombinedOutput(); err != nil {
|
||
|
|
return fmt.Errorf("enable service: %s: %w", strings.TrimSpace(string(out)), err)
|
||
|
|
}
|
||
|
|
fmt.Println(" Service enabled for autostart (default runlevel)")
|
||
|
|
|
||
|
|
return nil
|
||
|
|
}
|