@@ -22,9 +22,12 @@ the IP address, hostname, and services (description and status).
2222import  curses 
2323import  curses .ascii 
2424import  curses .textpad 
25+ import  re 
2526import  sys 
2627import  signal 
2728import  os 
29+ import  shutil 
30+ import  tempfile 
2831import  logging 
2932import  subprocess 
3033from  typing  import  List , Any , Tuple , Generator 
@@ -102,11 +105,66 @@ def get_keyboard_layout() -> str:
102105
103106def  set_keyboard_layout (layout : str ) ->  subprocess .CompletedProcess :
104107    """ 
105-     Set the keyboard layout based on the user selection. 
108+     Set the keyboard layout by editing /etc/default/keyboard and applying it with setupcon. 
109+     This avoids localectl (which is disabled on Debian/Ubuntu builds). 
110+ 
111+     Notes: 
112+       - Does NOT trigger update-initramfs; only applies to the running system's console. Delphix 
113+         doesn't have TTY interactions before pivot-root, e.g. encrypted root passphrase, and 
114+         rescue shell can be done with the default "us" layout. 
106115    """ 
107-     cmd : List [str ] =  ['localectl' , 'set-x11-keymap' , layout , 'pc105' ]
108-     subprocess .run (cmd , shell = False , check = True )
109-     return  subprocess .run ('setupcon' , shell = False , check = True )
116+     kb_path  =  "/etc/default/keyboard" 
117+     kb_backup  =  kb_path  +  ".bak" 
118+ 
119+     # Read current content (if file doesn't exist, start with a minimal stub) 
120+     try :
121+         with  open (kb_path , "r" , encoding = "utf-8" ) as  f :
122+             lines  =  f .readlines ()
123+     except  FileNotFoundError :
124+         lines  =  [
125+             'XKBMODEL="pc105"\n ' ,
126+             f'XKBLAYOUT="{ layout } \n ' ,
127+             'XKBVARIANT=""\n ' ,
128+             'XKBOPTIONS=""\n ' ,
129+             'BACKSPACE="guess"\n ' ,
130+         ]
131+     else :
132+         # Update or append XKBLAYOUT line 
133+         updated  =  False 
134+         pattern  =  re .compile (r'^\s*XKBLAYOUT\s*=' )
135+         for  i , line  in  enumerate (lines ):
136+             if  pattern .match (line ):
137+                 lines [i ] =  f'XKBLAYOUT="{ layout } \n ' 
138+                 updated  =  True 
139+                 break 
140+         if  not  updated :
141+             # Append if not present 
142+             lines .append (f'XKBLAYOUT="{ layout } \n ' )
143+ 
144+     # Atomic write with backup 
145+     os .makedirs (os .path .dirname (kb_path ), exist_ok = True )
146+     if  os .path .exists (kb_path ):
147+         shutil .copy2 (kb_path , kb_backup )
148+ 
149+     fd , tmp  =  tempfile .mkstemp (prefix = ".keyboard." ,
150+                                dir = os .path .dirname (kb_path ))
151+     try :
152+         with  os .fdopen (fd , "w" , encoding = "utf-8" ) as  f :
153+             f .writelines (lines )
154+         os .replace (tmp , kb_path )
155+     except  Exception :
156+         # Clean temp file and restore from backup if we wrote a partial file 
157+         try :
158+             os .remove (tmp )
159+         except  OSError :
160+             pass 
161+         if  os .path .exists (kb_backup ):
162+             shutil .copy2 (kb_backup , kb_path )
163+         raise 
164+ 
165+     # Apply to current console immediately (does not affect serial consoles) 
166+     # Use --force so it rebuilds/loads even if it thinks nothing changed. 
167+     return  subprocess .run (["setupcon" , "--force" ], shell = False , check = True )
110168
111169
112170def  get_valid_keyboard_layouts () ->  List [str ]:
0 commit comments