15.04.2026 Update
This commit is contained in:
@@ -105,6 +105,7 @@ type AppConfig struct {
|
||||
ClientPolicy ClientPolicyConfig `yaml:"client_policy,omitempty"`
|
||||
Connectivity ConnectivityConfig `yaml:"connectivity,omitempty" json:"connectivity,omitempty"`
|
||||
Auth AuthConfig `yaml:"auth,omitempty"`
|
||||
ListenAddresses []string `yaml:"listen_addresses,omitempty" json:"listen_addresses,omitempty"`
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
23
main.go
23
main.go
@@ -20,6 +20,7 @@ import (
|
||||
"nano-router/monitor"
|
||||
"nano-router/nat"
|
||||
"nano-router/network"
|
||||
"nano-router/setup"
|
||||
"nano-router/traffic"
|
||||
)
|
||||
|
||||
@@ -27,6 +28,11 @@ import (
|
||||
var publicFS embed.FS
|
||||
|
||||
func main() {
|
||||
if len(os.Args) > 1 && os.Args[1] == "setup" {
|
||||
setup.Run()
|
||||
return
|
||||
}
|
||||
|
||||
cfg, err := config.Load()
|
||||
if err != nil {
|
||||
log.Fatalf("load config.yaml: %v", err)
|
||||
@@ -164,8 +170,23 @@ func main() {
|
||||
handlers.StartPolicySync(30 * time.Second)
|
||||
|
||||
log.Printf("Config file: %s", config.GetPath())
|
||||
|
||||
handler := auth.PublicAuthMiddleware(mux)
|
||||
if len(cfg.ListenAddresses) > 0 {
|
||||
servers := make([]*http.Server, len(cfg.ListenAddresses))
|
||||
errCh := make(chan error, len(cfg.ListenAddresses))
|
||||
for i, addr := range cfg.ListenAddresses {
|
||||
bind := addr + ":" + port
|
||||
srv := &http.Server{Addr: bind, Handler: handler}
|
||||
servers[i] = srv
|
||||
log.Printf("Network Manager listening on http://%s", bind)
|
||||
go func(s *http.Server) { errCh <- s.ListenAndServe() }(srv)
|
||||
}
|
||||
log.Fatal(<-errCh)
|
||||
} else {
|
||||
log.Printf("Network Manager listening on http://0.0.0.0:%s", port)
|
||||
log.Fatal(http.ListenAndServe(":"+port, auth.PublicAuthMiddleware(mux)))
|
||||
log.Fatal(http.ListenAndServe(":"+port, handler))
|
||||
}
|
||||
}
|
||||
|
||||
func importSystemState() *config.AppConfig {
|
||||
|
||||
BIN
nano-router
BIN
nano-router
Binary file not shown.
631
setup/setup.go
Normal file
631
setup/setup.go
Normal file
@@ -0,0 +1,631 @@
|
||||
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
|
||||
}
|
||||
45
setup/termios_linux.go
Normal file
45
setup/termios_linux.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package setup
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type termios struct {
|
||||
Iflag uint32
|
||||
Oflag uint32
|
||||
Cflag uint32
|
||||
Lflag uint32
|
||||
Cc [20]byte
|
||||
Ispeed uint32
|
||||
Ospeed uint32
|
||||
}
|
||||
|
||||
const (
|
||||
tcgetattr = 0x5401
|
||||
tcsetattr = 0x5402
|
||||
TCSAFLUSH = 2
|
||||
ECHO = 0x8
|
||||
ICANON = 0x100
|
||||
)
|
||||
|
||||
func termiosGetState(fd int) (*termios, bool) {
|
||||
var state termios
|
||||
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), uintptr(tcgetattr), uintptr(unsafe.Pointer(&state))); err != 0 {
|
||||
return nil, false
|
||||
}
|
||||
return &state, true
|
||||
}
|
||||
|
||||
func termiosDisableEcho(fd int) {
|
||||
var state termios
|
||||
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), uintptr(tcgetattr), uintptr(unsafe.Pointer(&state))); err != 0 {
|
||||
return
|
||||
}
|
||||
state.Lflag &^= ECHO | ICANON
|
||||
syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), uintptr(tcsetattr), uintptr(unsafe.Pointer(&state)))
|
||||
}
|
||||
|
||||
func termiosRestore(fd int, state *termios) {
|
||||
syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), uintptr(tcsetattr), uintptr(unsafe.Pointer(state)))
|
||||
}
|
||||
Reference in New Issue
Block a user