Skip to content

Commit 812027e

Browse files
committed
resolved nmcli version
1 parent df6a15f commit 812027e

3 files changed

Lines changed: 114 additions & 28 deletions

File tree

Dockerfile

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,15 @@ RUN CGO_ENABLED=0 GOOS=linux go build -o /app ./cmd/blox && \
1717
CGO_ENABLED=0 GOOS=linux go build -o /initipfscluster ./modules/initipfscluster
1818

1919
# Final stage
20-
FROM alpine:3.17
20+
FROM alpine:3.20
2121

22-
# Install necessary packages
22+
# Install packages from stable repos (NM 1.46.6 — compatible with host NM 1.46.0)
23+
# IMPORTANT: Do NOT use edge/testing for networkmanager packages — a newer nmcli
24+
# than the host's NM daemon causes "unknown property" errors (e.g. mac-address-denylist)
2325
RUN apk update && \
24-
apk add --no-cache hostapd iw wireless-tools networkmanager-wifi networkmanager-cli dhcp iptables curl mergerfs --repository http://dl-cdn.alpinelinux.org/alpine/edge/testing
26+
apk add --no-cache hostapd iw wireless-tools networkmanager-wifi networkmanager-cli \
27+
networkmanager-dnsmasq dhcpcd iptables curl && \
28+
apk add --no-cache --repository http://dl-cdn.alpinelinux.org/alpine/edge/testing mergerfs
2529

2630
WORKDIR /
2731

wap/pkg/wifi/hotspot.go

Lines changed: 65 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bufio"
55
"context"
66
"fmt"
7+
"os"
78
"runtime"
89
"strings"
910
"time"
@@ -99,14 +100,7 @@ func CheckHotspotSupported(ctx context.Context) (supported bool, err error) {
99100
func StartHotspot(ctx context.Context, forceReload bool) error {
100101
var commands []string
101102
var err error
102-
// supported, err := CheckHotspotSupported(ctx)
103-
// if err != nil {
104-
// log.Errorw("failed to check hotspot support", "err", err)
105-
// return err
106-
// } else if !supported {
107-
// log.Errorw("hotspot not supported")
108-
// return fmt.Errorf("hotspot not supported")
109-
// }
103+
110104
switch runtime.GOOS {
111105
case "windows":
112106
commands = []string{"netsh wlan start hostednetwork"}
@@ -128,17 +122,71 @@ func StartHotspot(ctx context.Context, forceReload bool) error {
128122
case "darwin":
129123
commands = []string{"/System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/createbssid -n FxBlox"}
130124
default:
131-
commands = []string{"nmcli connection delete FxBlox", "nmcli connection add type wifi con-name FxBlox autoconnect no wifi.mode ap wifi.ssid FxBlox ipv4.method shared ipv6.method shared", "nmcli connection up FxBlox"}
125+
return startHotspotLinux(ctx)
132126
}
127+
133128
for _, command := range commands {
134-
_, _, err = runCommand(ctx, command)
129+
_, stderr, errCmd := runCommand(ctx, command)
135130
time.Sleep(2 * time.Second)
136-
if err != nil {
137-
log.Errorw("failed to stop wifi hotspot", "command", command, "err", err)
131+
if errCmd != nil {
132+
err = errCmd
133+
log.Errorw("failed to start wifi hotspot", "command", command, "err", errCmd, "stderr", stderr)
134+
}
135+
}
136+
if err != nil {
137+
return err
138+
}
139+
return nil
140+
}
141+
142+
// startHotspotLinux creates the FxBlox hotspot on Linux using nmcli,
143+
// with a file-based fallback if nmcli connection add fails (e.g. due to
144+
// nmcli/NM daemon version mismatch with mac-address-denylist property).
145+
func startHotspotLinux(ctx context.Context) error {
146+
// Step 1: Delete existing FxBlox connection (cleanup, errors are non-fatal)
147+
_, stderr, delErr := runCommand(ctx, "nmcli connection delete FxBlox")
148+
if delErr != nil {
149+
log.Infow("no existing FxBlox connection to delete (this is normal on first run)", "stderr", stderr)
150+
}
151+
time.Sleep(2 * time.Second)
152+
153+
// Step 2: Create FxBlox hotspot connection via nmcli
154+
addCmd := "nmcli connection add type wifi con-name FxBlox autoconnect no wifi.mode ap wifi.ssid FxBlox ipv4.method shared ipv6.method shared"
155+
_, stderr, err := runCommand(ctx, addCmd)
156+
if err != nil {
157+
log.Errorw("nmcli connection add failed, trying file-based fallback", "err", err, "stderr", stderr)
158+
159+
// Fallback: write .nmconnection file directly
160+
if fallbackErr := writeHotspotConnectionFile(); fallbackErr != nil {
161+
return fmt.Errorf("nmcli add failed (%w) and file fallback also failed (%v)", err, fallbackErr)
162+
}
163+
_, stderr, reloadErr := runCommand(ctx, "nmcli connection reload")
164+
if reloadErr != nil {
165+
log.Errorw("failed to reload connections after file fallback", "err", reloadErr, "stderr", stderr)
166+
return fmt.Errorf("nmcli reload failed after file fallback: %w", reloadErr)
138167
}
168+
log.Infow("FxBlox connection created via file-based fallback")
139169
}
170+
time.Sleep(2 * time.Second)
171+
172+
// Step 3: Activate FxBlox connection
173+
_, stderr, err = runCommand(ctx, "nmcli connection up FxBlox")
140174
if err != nil {
141-
log.Errorw("failed to start wifi hotspot", "command", commands, "err", err)
175+
log.Errorw("failed to activate FxBlox hotspot", "err", err, "stderr", stderr)
176+
return err
177+
}
178+
179+
return nil
180+
}
181+
182+
// writeHotspotConnectionFile writes a .nmconnection file for the FxBlox hotspot directly,
183+
// bypassing nmcli. This avoids version-mismatch issues where a newer nmcli sends
184+
// properties (like mac-address-denylist) that an older NM daemon doesn't recognize.
185+
func writeHotspotConnectionFile() error {
186+
content := "[connection]\nid=FxBlox\ntype=wifi\nautoconnect=false\n\n[wifi]\nmode=ap\nssid=FxBlox\n\n[ipv4]\nmethod=shared\n\n[ipv6]\nmethod=shared\n"
187+
path := "/etc/NetworkManager/system-connections/FxBlox.nmconnection"
188+
if err := os.WriteFile(path, []byte(content), 0600); err != nil {
189+
log.Errorw("failed to write fallback .nmconnection file", "path", path, "err", err)
142190
return err
143191
}
144192
return nil
@@ -201,7 +249,6 @@ func CheckConnection(timeout time.Duration) error {
201249

202250
func StopHotspot(ctx context.Context) error {
203251
var commands []string
204-
var err error
205252
// supported, err := CheckHotspotSupported(ctx)
206253
// if err != nil {
207254
// log.Errorw("failed to check hotspot support", "err", err)
@@ -220,10 +267,10 @@ func StopHotspot(ctx context.Context) error {
220267
commands = []string{"nmcli r wifi off", "nmcli r wifi on"}
221268
}
222269
for _, command := range commands {
223-
_, _, err = runCommand(ctx, command)
224-
if err != nil {
225-
log.Errorw("failed to stop wifi hotspot", "command", command, "err", err)
226-
return err
270+
_, stderr, errCmd := runCommand(ctx, command)
271+
if errCmd != nil {
272+
log.Errorw("failed to stop wifi hotspot", "command", command, "err", errCmd, "stderr", stderr)
273+
return errCmd
227274
}
228275
}
229276
return nil

wap/pkg/wifi/wifi.go

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package wifi
22

33
import (
44
"bufio"
5+
"bytes"
56
"context"
67
"errors"
78
"fmt"
@@ -357,21 +358,52 @@ func WifiRemoveall(ctx context.Context) WifiRemoveallResponse {
357358
}
358359

359360
func createConnection(ctx context.Context, connectionName, ssid, password string) error {
360-
// Create a connection
361+
// Create a connection via nmcli
361362
cmd1 := exec.CommandContext(ctx, "nmcli", "con", "add", "type", "wifi", "ifname", "*", "con-name", connectionName, "ssid", ssid)
362-
if err := runCommandDirect(cmd1); err != nil {
363-
return err
363+
var stderr1 bytes.Buffer
364+
cmd1.Stderr = &stderr1
365+
if err := cmd1.Run(); err != nil {
366+
log.Errorw("nmcli con add failed, trying file-based fallback", "err", err, "stderr", stderr1.String())
367+
368+
// Fallback: write .nmconnection file directly (avoids nmcli version mismatch issues)
369+
if fallbackErr := writeWifiConnectionFile(connectionName, ssid, password); fallbackErr != nil {
370+
return fmt.Errorf("nmcli add failed (%w) and file fallback also failed (%v)", err, fallbackErr)
371+
}
372+
_, reloadStderr, reloadErr := runCommand(ctx, "nmcli connection reload")
373+
if reloadErr != nil {
374+
log.Errorw("failed to reload connections after file fallback", "err", reloadErr, "stderr", reloadStderr)
375+
return fmt.Errorf("nmcli reload failed after file fallback: %w", reloadErr)
376+
}
377+
log.Infow("WiFi connection created via file-based fallback", "connection", connectionName)
378+
return nil
364379
}
365380

366381
// Modify the connection with security settings
367382
cmd2 := exec.CommandContext(ctx, "nmcli", "con", "modify", connectionName, "wifi-sec.key-mgmt", "wpa-psk", "wifi-sec.psk", password)
368-
if err := runCommandDirect(cmd2); err != nil {
383+
var stderr2 bytes.Buffer
384+
cmd2.Stderr = &stderr2
385+
if err := cmd2.Run(); err != nil {
386+
log.Errorw("nmcli con modify failed", "err", err, "stderr", stderr2.String())
369387
return err
370388
}
371389

372390
return nil
373391
}
374392

393+
// writeWifiConnectionFile writes a .nmconnection file for a WiFi client connection directly,
394+
// bypassing nmcli. This avoids version-mismatch issues where a newer nmcli sends
395+
// properties (like mac-address-denylist) that an older NM daemon doesn't recognize.
396+
func writeWifiConnectionFile(connectionName, ssid, password string) error {
397+
content := fmt.Sprintf("[connection]\nid=%s\ntype=wifi\ninterface-name=*\n\n[wifi]\nssid=%s\n\n[wifi-security]\nkey-mgmt=wpa-psk\npsk=%s\n\n[ipv4]\nmethod=auto\n\n[ipv6]\nmethod=auto\n",
398+
connectionName, ssid, password)
399+
path := fmt.Sprintf("/etc/NetworkManager/system-connections/%s.nmconnection", connectionName)
400+
if err := os.WriteFile(path, []byte(content), 0600); err != nil {
401+
log.Errorw("failed to write fallback .nmconnection file", "path", path, "err", err)
402+
return err
403+
}
404+
return nil
405+
}
406+
375407
func getWifiConnections(ctx context.Context) ([]string, error) {
376408
stdout, _, err := runCommand(ctx, "nmcli --terse --fields TYPE,NAME connection")
377409
if err != nil {
@@ -607,16 +639,19 @@ func disconnectLinux(ctx context.Context) error {
607639
// This is used as a fallback when Wi-Fi connection fails
608640
func EnsureHotspotActive(ctx context.Context) error {
609641
// Check if FxBlox connection exists and is active
610-
stdout, _, err := runCommand(ctx, "nmcli -t -f NAME,STATE connection show --active")
642+
stdout, stderr, err := runCommand(ctx, "nmcli -t -f NAME,STATE connection show --active")
611643
if err == nil && strings.Contains(stdout, "FxBlox:activated") {
612644
log.Info("FxBlox hotspot is already active")
613645
return nil
614646
}
647+
if err != nil {
648+
log.Warnf("Failed to check active connections: %v, stderr: %s", err, stderr)
649+
}
615650

616651
// Try to activate existing FxBlox connection
617-
_, _, err = runCommand(ctx, "nmcli connection up FxBlox")
652+
_, stderr, err = runCommand(ctx, "nmcli connection up FxBlox")
618653
if err != nil {
619-
log.Warnf("Failed to activate existing FxBlox hotspot: %v", err)
654+
log.Warnf("Failed to activate existing FxBlox hotspot: %v, stderr: %s", err, stderr)
620655

621656
// If activation fails, try to recreate the hotspot
622657
return StartHotspot(ctx, true)

0 commit comments

Comments
 (0)