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) {
99100func 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]\n id=FxBlox\n type=wifi\n autoconnect=false\n \n [wifi]\n mode=ap\n ssid=FxBlox\n \n [ipv4]\n method=shared\n \n [ipv6]\n method=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
202250func 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
0 commit comments