// Usage: go run hotspot.go [up|down|remove] [ssid] [password] package main import ( "fmt" "log" "os" "time" "github.com/godbus/dbus/v5" ) const ( ourUUID = "7d706027-727c-4d4c-a816-f0e1b99db8ab" ) var ( defaultSSID = "PIAP" defaultPassword = "raspberry" ) func usage() { fmt.Printf("Usage: %s [up|down|remove] [ssid] [password]\n", os.Args[0]) os.Exit(0) } func startAccessPoint(conn *dbus.Conn, connPath, devPath dbus.ObjectPath) error { nmObj := conn.Object( "org.freedesktop.NetworkManager", "/org/freedesktop/NetworkManager", ) // Activate the connection var activePath dbus.ObjectPath err := nmObj. Call("org.freedesktop.NetworkManager.ActivateConnection", 0, connPath, devPath, dbus.ObjectPath("/")). Store(&activePath) if err != nil { return fmt.Errorf("ActivateConnection failed: %v", err) } // Wait until the connection is activated props := conn.Object( "org.freedesktop.NetworkManager", activePath, ) start := time.Now() for time.Since(start) < 10*time.Second { var stateVar dbus.Variant err = props. Call("org.freedesktop.DBus.Properties.Get", 0, "org.freedesktop.NetworkManager.Connection.Active", "State"). Store(&stateVar) if err != nil { return fmt.Errorf("Properties.Get(State) failed: %v", err) } if state, ok := stateVar.Value().(uint32); ok && state == 2 { fmt.Println("Access point started") return nil } time.Sleep(1 * time.Second) } return fmt.Errorf("failed to start access point") } func stopAccessPoint(conn *dbus.Conn, devPath dbus.ObjectPath) error { devObj := conn.Object( "org.freedesktop.NetworkManager", devPath, ) err := devObj. Call("org.freedesktop.NetworkManager.Device.Disconnect", 0). Err if err != nil { return fmt.Errorf("Device.Disconnect failed: %v", err) } fmt.Println("Access point stopped") return nil } func removeConnection(conn *dbus.Conn, connPath dbus.ObjectPath) error { connObj := conn.Object( "org.freedesktop.NetworkManager", connPath, ) err := connObj. Call("org.freedesktop.NetworkManager.Settings.Connection.Delete", 0). Err if err != nil { return fmt.Errorf("Connection.Delete failed: %v", err) } fmt.Println("Connection removed") return nil } func getSettings(op string) (string, string) { // Get SSID and password from args, env vars or use defaults ssid := defaultSSID password := defaultPassword // Check environment variables first if v, ok := os.LookupEnv("WIFI_SSID"); ok { ssid = v } if v, ok := os.LookupEnv("WIFI_PASSWORD"); ok { password = v } // Command line args override environment variables if op == "up" && len(os.Args) >= 5 { ssid = os.Args[3] password = os.Args[4] } return ssid, password } func getConnectionPath(conn *dbus.Conn, connUUID string) (dbus.ObjectPath, error) { // Get the Settings interface settingsObj := conn.Object( "org.freedesktop.NetworkManager", "/org/freedesktop/NetworkManager/Settings", ) // List existing connections var paths []dbus.ObjectPath err := settingsObj. Call("org.freedesktop.NetworkManager.Settings.ListConnections", 0). Store(&paths) if err != nil { return "", fmt.Errorf("ListConnections failed: %v", err) } // Look up our connection by UUID var connPath dbus.ObjectPath for _, p := range paths { obj := conn.Object( "org.freedesktop.NetworkManager", p, ) var cfg map[string]map[string]dbus.Variant err = obj. Call("org.freedesktop.NetworkManager.Settings.Connection.GetSettings", 0). Store(&cfg) if err != nil { continue } if v, ok := cfg["connection"]["uuid"].Value().(string); ok && v == connUUID { connPath = p break } } return connPath, nil } func addConnection(conn *dbus.Conn, ssid string, password string) (dbus.ObjectPath, error) { settingsObj := conn.Object( "org.freedesktop.NetworkManager", "/org/freedesktop/NetworkManager/Settings", ) settingsMap := map[string]map[string]dbus.Variant{ "connection": { "type": dbus.MakeVariant("802-11-wireless"), "uuid": dbus.MakeVariant(ourUUID), "id": dbus.MakeVariant(ssid), "autoconnect": dbus.MakeVariant(true), }, "802-11-wireless": { "ssid": dbus.MakeVariant([]byte(ssid)), "mode": dbus.MakeVariant("ap"), "band": dbus.MakeVariant("bg"), "channel": dbus.MakeVariant(uint32(1)), }, "802-11-wireless-security": { "key-mgmt": dbus.MakeVariant("wpa-psk"), "psk": dbus.MakeVariant(password), }, "ipv4": { "method": dbus.MakeVariant("shared"), }, "ipv6": { "method": dbus.MakeVariant("ignore"), }, } var connPath dbus.ObjectPath err := settingsObj. Call("org.freedesktop.NetworkManager.Settings.AddConnection", 0, settingsMap). Store(&connPath) if err != nil { return "", fmt.Errorf("AddConnection failed: %v", err) } return connPath, nil } func getDevicePath(conn *dbus.Conn, iface string) (dbus.ObjectPath, error) { // Get the NetworkManager interface nmObj := conn.Object( "org.freedesktop.NetworkManager", "/org/freedesktop/NetworkManager", ) // Find the device by interface name var devPath dbus.ObjectPath err := nmObj. Call("org.freedesktop.NetworkManager.GetDeviceByIpIface", 0, iface). Store(&devPath) if err != nil { return "", fmt.Errorf("GetDeviceByIpIface(%s) failed: %v", iface, err) } return devPath, nil } func main() { if len(os.Args) < 3 { usage() } iface := os.Args[1] op := os.Args[2] ssid, password := getSettings(op) // Connect to the system bus conn, err := dbus.SystemBus() if err != nil { log.Fatalf("Failed to connect to system bus: %v", err) } connPath, err := getConnectionPath(conn, ourUUID) if err != nil { log.Fatal(err) } if connPath == "" { connPath, err = addConnection(conn, ssid, password) if err != nil { log.Fatal(err) } } devPath, err := getDevicePath(conn, iface) if err != nil { log.Fatal(err) } switch op { case "up": if err := startAccessPoint(conn, connPath, devPath); err != nil { log.Fatal(err) } os.Exit(0) case "down": if err := stopAccessPoint(conn, devPath); err != nil { log.Fatal(err) } os.Exit(0) case "remove": if connPath == "" { fmt.Println("No connection found to remove") os.Exit(0) } if err := removeConnection(conn, connPath); err != nil { log.Fatal(err) } os.Exit(0) default: usage() } }