@@ -175,6 +175,89 @@ func getMachineInfoFromMaas(nodeName, maasAPIKey, maasEndpoint string) (zone, re
175175 return zone , resourcePool , bootInterface , nil
176176}
177177
178+ // registerWithMAAS registers the node as an LXD VM host in MAAS (idempotent)
179+ func registerWithMAAS (maasEndpoint , maasAPIKey , systemID , nodeIP , trustPassword , zone , resourcePool , hostName string ) error {
180+ if maasEndpoint == "" || maasAPIKey == "" {
181+ return fmt .Errorf ("MAAS credentials unavailable" )
182+ }
183+ ctx := context .Background ()
184+ client := maasclient .NewAuthenticatedClientSet (maasEndpoint , maasAPIKey )
185+
186+ // Guard 1: Verify the MAAS machine identified by systemID owns nodeIP (or derive power IP)
187+ m , err := client .Machines ().Machine (systemID ).Get (ctx )
188+ if err != nil {
189+ return fmt .Errorf ("get machine %s: %w" , systemID , err )
190+ }
191+ hostIP := nodeIP
192+ owns := false
193+ for _ , ip := range m .IPAddresses () {
194+ if ip .String () == nodeIP {
195+ owns = true
196+ break
197+ }
198+ }
199+ if ! owns {
200+ ips := m .IPAddresses ()
201+ if len (ips ) == 0 {
202+ return fmt .Errorf ("ownership check failed: system-id %s has no IPs; cannot register" , systemID )
203+ }
204+ hostIP = ips [0 ].String ()
205+ }
206+
207+ // Idempotency/conflict checks via API for speed
208+ hosts , err := client .VMHosts ().List (ctx , nil )
209+ if err != nil {
210+ return fmt .Errorf ("list vm hosts: %w" , err )
211+ }
212+ wantHost := fmt .Sprintf ("https://%s:8443" , hostIP )
213+ for _ , h := range hosts {
214+ if h .PowerAddress () == wantHost && h .HostSystemID () != "" && h .HostSystemID () != systemID {
215+ return fmt .Errorf ("conflict: existing VM host %s uses %s mapped to %s" , h .Name (), wantHost , h .HostSystemID ())
216+ }
217+ if h .Name () == hostName || h .PowerAddress () == wantHost {
218+ log .Printf ("MAAS VM host already present: name=%s power_address=%s" , h .Name (), h .PowerAddress ())
219+ return nil
220+ }
221+ }
222+
223+ // Prefer MAAS CLI for creation to match manual success path
224+ if _ , err := exec .LookPath ("maas" ); err == nil {
225+ profile := "ds"
226+ // Non-interactive login (idempotent)
227+ _ = runCmd ("maas" , []string {"login" , profile , maasEndpoint , maasAPIKey })
228+ args := []string {profile , "vm-hosts" , "create" , "type=lxd" , fmt .Sprintf ("power_address=%s" , wantHost ), fmt .Sprintf ("password=%s" , trustPassword ), fmt .Sprintf ("name=%s" , hostName )}
229+ // Do not pass zone/pool on create
230+ if err := runCmd ("maas" , args ); err != nil {
231+ return fmt .Errorf ("maas cli create failed: %w" , err )
232+ }
233+ log .Printf ("MAAS VM host registered via CLI: %s (%s)" , hostName , wantHost )
234+ return nil
235+ }
236+
237+ // Fallback: minimal API create
238+ params := maasclient .ParamsBuilder ().
239+ Set ("type" , "lxd" ).
240+ Set ("power_address" , wantHost ).
241+ Set ("name" , hostName )
242+ if trustPassword != "" {
243+ params .Set ("password" , trustPassword )
244+ }
245+ if _ , err := client .VMHosts ().Create (ctx , params ); err != nil {
246+ return fmt .Errorf ("create vm host: %w" , err )
247+ }
248+ log .Printf ("MAAS VM host registered via API: %s (%s)" , hostName , wantHost )
249+ return nil
250+ }
251+
252+ func runCmd (bin string , args []string ) error {
253+ cmd := exec .Command (bin , args ... )
254+ out , err := cmd .CombinedOutput ()
255+ if err != nil {
256+ return fmt .Errorf ("%s %v: %s" , bin , args , string (out ))
257+ }
258+ return nil
259+ }
260+
178261func main () {
179262 log .Println ("Starting LXD initializer" )
180263
@@ -218,7 +301,13 @@ func main() {
218301 maasAPIKey := * maasAPIKeyFlag
219302 maasEndpoint := * maasEndpointFlag
220303
221- // If flags are not provided, try to read from the Kubernetes secret
304+ // If flags are not provided, use env (set by DS rendering) or try to read from the Kubernetes secret
305+ if maasEndpoint == "" {
306+ maasEndpoint = os .Getenv ("MAAS_ENDPOINT" )
307+ }
308+ if maasAPIKey == "" {
309+ maasAPIKey = os .Getenv ("MAAS_API_KEY" )
310+ }
222311 if maasAPIKey == "" || maasEndpoint == "" {
223312 if secretEndpoint , secretAPIKey , err := getMaasCredentialsFromSecret (); err == nil {
224313 if maasEndpoint == "" {
@@ -245,7 +334,16 @@ func main() {
245334 storageSize = "50"
246335 }
247336
248- nicType := "macvlan"
337+ // Determine NIC type and parent
338+ // NIC_TYPE env supports values like "bridge"/"bridged" or "macvlan". Default to bridge.
339+ nicTypeEnv := os .Getenv ("NIC_TYPE" )
340+ if nicTypeEnv == "" {
341+ nicTypeEnv = "bridge"
342+ }
343+ nicMode := strings .ToLower (nicTypeEnv )
344+ if nicMode == "bridged" {
345+ nicMode = "bridge"
346+ }
249347
250348 networkBridge := * networkBridgeFlag
251349 if networkBridge == "" {
@@ -264,9 +362,19 @@ func main() {
264362 log .Printf ("Resource pool retrieved from MAAS: %s" , resourcePool )
265363 log .Printf ("Boot interface retrieved from MAAS: %s" , bootInterfaceName )
266364
267- nicParent := bootInterfaceName
365+ nicParent := os .Getenv ("NIC_PARENT" )
366+ if nicParent == "" {
367+ nicParent = bootInterfaceName
368+ }
268369
269- log .Printf ("Using NIC type=%s parent=%s" , nicType , nicParent )
370+ // Log final NIC config
371+ log .Printf ("Using NIC mode=%s (device nictype=%s) parent=%s" , nicMode , func () string {
372+ if nicMode == "bridge" {
373+ return "bridged"
374+ } else {
375+ return nicMode
376+ }
377+ }(), nicParent )
270378
271379 skipNetworkUpdate := * skipNetworkUpdateFlag
272380 if ! skipNetworkUpdate {
@@ -290,7 +398,7 @@ func main() {
290398 // Perform actions based on the specified action
291399 if actionStr == "init" || actionStr == "both" {
292400 // Initialize LXD
293- if err := initializeLXD (storageBackend , storageSize , networkBridge , skipNetworkUpdate , trustPassword , nicType , nicParent ); err != nil {
401+ if err := initializeLXD (storageBackend , storageSize , networkBridge , skipNetworkUpdate , trustPassword , nicMode , nicParent ); err != nil {
294402 log .Fatalf ("Failed to initialize LXD: %v" , err )
295403 }
296404
@@ -305,6 +413,18 @@ func main() {
305413 }
306414 }
307415
416+ if actionStr == "register" || actionStr == "both" {
417+ // Build a stable host name using MAAS system-id
418+ systemID , sErr := extractSystemIDFromNodeName (nodeName )
419+ if sErr != nil {
420+ log .Fatalf ("Failed to extract system ID from node name: %v" , sErr )
421+ }
422+ hostName := fmt .Sprintf ("lxd-host-%s" , systemID )
423+ if err := registerWithMAAS (maasEndpoint , maasAPIKey , systemID , nodeIP , trustPassword , zone , resourcePool , hostName ); err != nil {
424+ log .Fatalf ("Failed to register LXD host in MAAS: %v" , err )
425+ }
426+ }
427+
308428 // If running as a standalone binary, exit after completing the actions
309429 if actionStr == "once" {
310430 log .Println ("Actions completed successfully" )
@@ -602,6 +722,11 @@ func ensureMAASProfile(c lxdclient.InstanceServer, nicType, nicParent, pool stri
602722 return nil // already present
603723 }
604724 }
725+ // Map network mode to device nictype expected by LXD
726+ deviceNictype := nicType
727+ if deviceNictype == "bridge" {
728+ deviceNictype = "bridged"
729+ }
605730 profile := api.ProfilesPost {
606731 Name : profileName ,
607732 ProfilePut : api.ProfilePut {
@@ -614,7 +739,7 @@ func ensureMAASProfile(c lxdclient.InstanceServer, nicType, nicParent, pool stri
614739 },
615740 "eth0" : {
616741 "type" : "nic" ,
617- "nictype" : nicType ,
742+ "nictype" : deviceNictype ,
618743 "parent" : nicParent ,
619744 "name" : "eth0" ,
620745 },
0 commit comments